mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-18 09:11:28 +00:00
Merge branch 'todo'
This commit is contained in:
commit
3fd4154ddd
@ -14422,3 +14422,19 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.TitleJoinGroupCall" = "Join";
|
"Chat.TitleJoinGroupCall" = "Join";
|
||||||
"Invitation.JoinGroupCall.EnableMicrophone" = "Switch on the microphone";
|
"Invitation.JoinGroupCall.EnableMicrophone" = "Switch on the microphone";
|
||||||
"Chat.PinnedGroupCallTitle" = "Pinned Group Call";
|
"Chat.PinnedGroupCallTitle" = "Pinned Group Call";
|
||||||
|
|
||||||
|
"Notification.TodoTasks_1" = "%@ task";
|
||||||
|
"Notification.TodoTasks_any" = "%@ tasks";
|
||||||
|
|
||||||
|
"Notification.TodoCompleted" = "%1$@ marked \"%2$@\" as done.";
|
||||||
|
"Notification.TodoIncompleted" = "%1$@ marked \"%2$@\" as undone.";
|
||||||
|
"Notification.TodoAddedTask" = "%1$@ added a new task \"%2$@\" to \"%3$@\".";
|
||||||
|
"Notification.TodoAddedMultipleTasks" = "%1$@ added %2$@ to \"%3$@\".";
|
||||||
|
|
||||||
|
"Notification.TodoCompletedYou" = "You marked \"%1$@\" as done.";
|
||||||
|
"Notification.TodoIncompletedYou" = "You marked \"%1$@\" as not done.";
|
||||||
|
"Notification.TodoAddedTaskYou" = "You added a new task \"%1$@\" to \"%2$@\".";
|
||||||
|
"Notification.TodoAddedMultipleTasksYou" = "You added %1$@ to \"%2$@\".";
|
||||||
|
|
||||||
|
"Stars.Transaction.GiftTransfer" = "Gift Transfer";
|
||||||
|
"Stars.Intro.Transaction.GiftTransfer" = "Gift Transfer";
|
||||||
|
@ -43,6 +43,7 @@ public enum PremiumIntroSource {
|
|||||||
case animatedEmoji
|
case animatedEmoji
|
||||||
case messageEffects
|
case messageEffects
|
||||||
case paidMessages
|
case paidMessages
|
||||||
|
case todo
|
||||||
case auth(String)
|
case auth(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ public enum PremiumDemoSubject {
|
|||||||
case business
|
case business
|
||||||
case messageEffects
|
case messageEffects
|
||||||
case paidMessages
|
case paidMessages
|
||||||
|
case todo
|
||||||
|
|
||||||
case businessLocation
|
case businessLocation
|
||||||
case businessHours
|
case businessHours
|
||||||
|
@ -21,6 +21,7 @@ public enum AttachmentButtonType: Equatable {
|
|||||||
case gallery
|
case gallery
|
||||||
case file
|
case file
|
||||||
case location
|
case location
|
||||||
|
case todo
|
||||||
case quickReply
|
case quickReply
|
||||||
case contact
|
case contact
|
||||||
case poll
|
case poll
|
||||||
@ -36,6 +37,8 @@ public enum AttachmentButtonType: Equatable {
|
|||||||
return "file"
|
return "file"
|
||||||
case .location:
|
case .location:
|
||||||
return "location"
|
return "location"
|
||||||
|
case .todo:
|
||||||
|
return "todo"
|
||||||
case .quickReply:
|
case .quickReply:
|
||||||
return "quickReply"
|
return "quickReply"
|
||||||
case .contact:
|
case .contact:
|
||||||
@ -71,6 +74,12 @@ public enum AttachmentButtonType: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .todo:
|
||||||
|
if case .todo = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case .quickReply:
|
case .quickReply:
|
||||||
if case .quickReply = rhs {
|
if case .quickReply = rhs {
|
||||||
return true
|
return true
|
||||||
|
@ -195,6 +195,9 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
case .location:
|
case .location:
|
||||||
name = strings.Attachment_Location
|
name = strings.Attachment_Location
|
||||||
imageName = "Chat/Attach Menu/Location"
|
imageName = "Chat/Attach Menu/Location"
|
||||||
|
case .todo:
|
||||||
|
name = "To Do List"
|
||||||
|
imageName = "Chat/Attach Menu/Todo"
|
||||||
case .contact:
|
case .contact:
|
||||||
name = strings.Attachment_Contact
|
name = strings.Attachment_Contact
|
||||||
imageName = "Chat/Attach Menu/Contact"
|
imageName = "Chat/Attach Menu/Contact"
|
||||||
@ -1267,6 +1270,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}, openBoostToUnrestrict: {
|
}, openBoostToUnrestrict: {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
|
}, editTodoMessage: { _, _ in
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
@ -1490,6 +1494,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
accessibilityTitle = self.presentationData.strings.Attachment_File
|
accessibilityTitle = self.presentationData.strings.Attachment_File
|
||||||
case .location:
|
case .location:
|
||||||
accessibilityTitle = self.presentationData.strings.Attachment_Location
|
accessibilityTitle = self.presentationData.strings.Attachment_Location
|
||||||
|
case .todo:
|
||||||
|
//TODO:localize
|
||||||
|
accessibilityTitle = "To Do List"
|
||||||
case .contact:
|
case .contact:
|
||||||
accessibilityTitle = self.presentationData.strings.Attachment_Contact
|
accessibilityTitle = self.presentationData.strings.Attachment_Contact
|
||||||
case .poll:
|
case .poll:
|
||||||
|
@ -333,7 +333,8 @@ public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: P
|
|||||||
if let cutoutRect {
|
if let cutoutRect {
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
context.fillEllipse(in: cutoutRect.offsetBy(dx: 0.0, dy: size.height - cutoutRect.maxY - cutoutRect.height))
|
//TODO:fix
|
||||||
|
context.fillEllipse(in: cutoutRect)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let unroundedImage: UIImage?
|
let unroundedImage: UIImage?
|
||||||
|
@ -176,6 +176,8 @@ public final class BrowserBookmarksScreen: ViewController {
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||||
"//submodules/ComposePollUI",
|
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
@ -20,7 +20,7 @@ import AsyncDisplayKit
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import PeerNameColorItem
|
import PeerNameColorItem
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
import ComposePollUI
|
import ListComposePollOptionComponent
|
||||||
import ChatEntityKeyboardInputNode
|
import ChatEntityKeyboardInputNode
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
@ -8,7 +8,7 @@ import TextNodeWithEntities
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import ItemListUI
|
import ItemListUI
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import ComposePollUI
|
import ListComposePollOptionComponent
|
||||||
import TextFieldComponent
|
import TextFieldComponent
|
||||||
|
|
||||||
public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
|
public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
|
||||||
|
@ -423,6 +423,20 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
|||||||
if messageText.isEmpty, case let .Loaded(content) = webpage.content {
|
if messageText.isEmpty, case let .Loaded(content) = webpage.content {
|
||||||
messageText = content.displayUrl
|
messageText = content.displayUrl
|
||||||
}
|
}
|
||||||
|
case let todo as TelegramMediaTodo:
|
||||||
|
let pollPrefix = "☑️ "
|
||||||
|
let entityOffset = (pollPrefix as NSString).length
|
||||||
|
messageText = "\(pollPrefix)\(todo.text)"
|
||||||
|
for entity in todo.textEntities {
|
||||||
|
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||||
|
if customEmojiRanges == nil {
|
||||||
|
customEmojiRanges = []
|
||||||
|
}
|
||||||
|
let range = NSRange(location: entityOffset + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
||||||
|
let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile)
|
||||||
|
customEmojiRanges?.append((range, attribute))
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
public let openBoostToUnrestrict: () -> Void
|
public let openBoostToUnrestrict: () -> Void
|
||||||
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
|
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
|
||||||
public let dismissAllTooltips: () -> Void
|
public let dismissAllTooltips: () -> Void
|
||||||
|
public let editTodoMessage: (MessageId, Bool) -> Void
|
||||||
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||||
public let chatController: () -> ViewController?
|
public let chatController: () -> ViewController?
|
||||||
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||||
@ -293,6 +294,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
openBoostToUnrestrict: @escaping () -> Void,
|
openBoostToUnrestrict: @escaping () -> Void,
|
||||||
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
|
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
|
||||||
dismissAllTooltips: @escaping () -> Void,
|
dismissAllTooltips: @escaping () -> Void,
|
||||||
|
editTodoMessage: @escaping (MessageId, Bool) -> Void,
|
||||||
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
|
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
|
||||||
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
||||||
toggleChatSidebarMode: @escaping () -> Void,
|
toggleChatSidebarMode: @escaping () -> Void,
|
||||||
@ -412,6 +414,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
self.openBoostToUnrestrict = openBoostToUnrestrict
|
self.openBoostToUnrestrict = openBoostToUnrestrict
|
||||||
self.updateRecordingTrimRange = updateRecordingTrimRange
|
self.updateRecordingTrimRange = updateRecordingTrimRange
|
||||||
self.dismissAllTooltips = dismissAllTooltips
|
self.dismissAllTooltips = dismissAllTooltips
|
||||||
|
self.editTodoMessage = editTodoMessage
|
||||||
self.updateHistoryFilter = updateHistoryFilter
|
self.updateHistoryFilter = updateHistoryFilter
|
||||||
self.updateChatLocationThread = updateChatLocationThread
|
self.updateChatLocationThread = updateChatLocationThread
|
||||||
self.toggleChatSidebarMode = toggleChatSidebarMode
|
self.toggleChatSidebarMode = toggleChatSidebarMode
|
||||||
@ -540,6 +543,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
}, openBoostToUnrestrict: {
|
}, openBoostToUnrestrict: {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
|
}, editTodoMessage: { _, _ in
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
@ -41,6 +41,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -26,6 +26,49 @@ import ChatPresentationInterfaceState
|
|||||||
import EmojiSuggestionsComponent
|
import EmojiSuggestionsComponent
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import TextFieldComponent
|
import TextFieldComponent
|
||||||
|
import ListComposePollOptionComponent
|
||||||
|
|
||||||
|
public final class ComposedPoll {
|
||||||
|
public struct Text {
|
||||||
|
public let string: String
|
||||||
|
public let entities: [MessageTextEntity]
|
||||||
|
|
||||||
|
public init(string: String, entities: [MessageTextEntity]) {
|
||||||
|
self.string = string
|
||||||
|
self.entities = entities
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let publicity: TelegramMediaPollPublicity
|
||||||
|
public let kind: TelegramMediaPollKind
|
||||||
|
|
||||||
|
public let text: Text
|
||||||
|
public let options: [TelegramMediaPollOption]
|
||||||
|
public let correctAnswers: [Data]?
|
||||||
|
public let results: TelegramMediaPollResults
|
||||||
|
public let deadlineTimeout: Int32?
|
||||||
|
public let usedCustomEmojiFiles: [Int64: TelegramMediaFile]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
publicity: TelegramMediaPollPublicity,
|
||||||
|
kind: TelegramMediaPollKind,
|
||||||
|
text: Text,
|
||||||
|
options: [TelegramMediaPollOption],
|
||||||
|
correctAnswers: [Data]?,
|
||||||
|
results: TelegramMediaPollResults,
|
||||||
|
deadlineTimeout: Int32?,
|
||||||
|
usedCustomEmojiFiles: [Int64: TelegramMediaFile]
|
||||||
|
) {
|
||||||
|
self.publicity = publicity
|
||||||
|
self.kind = kind
|
||||||
|
self.text = text
|
||||||
|
self.options = options
|
||||||
|
self.correctAnswers = correctAnswers
|
||||||
|
self.results = results
|
||||||
|
self.deadlineTimeout = deadlineTimeout
|
||||||
|
self.usedCustomEmojiFiles = usedCustomEmojiFiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ComposePollScreenComponent: Component {
|
final class ComposePollScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
@ -1585,7 +1628,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
|
|||||||
maxPollAnwsersCount = Int(value)
|
maxPollAnwsersCount = Int(value)
|
||||||
}
|
}
|
||||||
return InitialData(
|
return InitialData(
|
||||||
maxPollTextLength: Int(200),
|
maxPollTextLength: 200,
|
||||||
maxPollOptionLength: 100,
|
maxPollOptionLength: 100,
|
||||||
maxPollAnwsersCount: maxPollAnwsersCount
|
maxPollAnwsersCount: maxPollAnwsersCount
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,274 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ItemListUI
|
|
||||||
import PresentationDataUtils
|
|
||||||
|
|
||||||
class CreatePollOptionActionItem: ListViewItem, ItemListItem {
|
|
||||||
let theme: PresentationTheme
|
|
||||||
let title: String
|
|
||||||
let enabled: Bool
|
|
||||||
let tag: ItemListItemTag?
|
|
||||||
let sectionId: ItemListSectionId
|
|
||||||
let action: () -> Void
|
|
||||||
|
|
||||||
init(theme: PresentationTheme, title: String, enabled: Bool, tag: ItemListItemTag?, sectionId: ItemListSectionId, action: @escaping () -> Void) {
|
|
||||||
self.theme = theme
|
|
||||||
self.title = title
|
|
||||||
self.enabled = enabled
|
|
||||||
self.tag = tag
|
|
||||||
self.sectionId = sectionId
|
|
||||||
self.action = action
|
|
||||||
}
|
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
|
||||||
async {
|
|
||||||
let node = CreatePollOptionActionItemNode()
|
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
|
||||||
node.insets = layout.insets
|
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(node, {
|
|
||||||
return (nil, { _ in apply(false) })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let nodeValue = node() as? CreatePollOptionActionItemNode {
|
|
||||||
let makeLayout = nodeValue.asyncLayout()
|
|
||||||
|
|
||||||
var animated = true
|
|
||||||
if case .None = animation {
|
|
||||||
animated = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async {
|
|
||||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(layout, { _ in
|
|
||||||
apply(animated)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectable: Bool {
|
|
||||||
return self.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func selected(listView: ListView){
|
|
||||||
listView.clearHighlightAnimated(true)
|
|
||||||
self.action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let titleFont = Font.regular(17.0)
|
|
||||||
|
|
||||||
class CreatePollOptionActionItemNode: ListViewItemNode, ItemListItemNode {
|
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let topStripeNode: ASDisplayNode
|
|
||||||
private let bottomStripeNode: ASDisplayNode
|
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
|
||||||
private let titleNode: TextNode
|
|
||||||
|
|
||||||
private var item: CreatePollOptionActionItem?
|
|
||||||
|
|
||||||
var tag: ItemListItemTag? {
|
|
||||||
return self.item?.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
|
||||||
self.topStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.bottomStripeNode = ASDisplayNode()
|
|
||||||
self.bottomStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
|
||||||
self.iconNode.isLayerBacked = true
|
|
||||||
self.iconNode.displayWithoutProcessing = true
|
|
||||||
self.iconNode.displaysAsynchronously = false
|
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
|
||||||
self.titleNode.contentMode = .left
|
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
|
||||||
self.highlightedBackgroundNode.isLayerBacked = true
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
|
||||||
|
|
||||||
self.addSubnode(self.iconNode)
|
|
||||||
self.addSubnode(self.titleNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: CreatePollOptionActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
|
||||||
|
|
||||||
let currentItem = self.item
|
|
||||||
|
|
||||||
return { item, params, neighbors in
|
|
||||||
var updatedTheme: PresentationTheme?
|
|
||||||
var updatedIcon: UIImage?
|
|
||||||
|
|
||||||
if currentItem?.theme !== item.theme {
|
|
||||||
updatedTheme = item.theme
|
|
||||||
updatedIcon = PresentationResourcesItemList.addPhoneIcon(item.theme)
|
|
||||||
}
|
|
||||||
let leftInset: CGFloat = 60.0 + params.leftInset
|
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
|
||||||
let contentSize = CGSize(width: params.width, height: 44.0)
|
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
|
||||||
let layoutSize = layout.size
|
|
||||||
|
|
||||||
return (layout, { [weak self] animated in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.item = item
|
|
||||||
|
|
||||||
if let _ = updatedTheme {
|
|
||||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
|
||||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
|
||||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = titleApply()
|
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition
|
|
||||||
if animated {
|
|
||||||
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
|
||||||
} else {
|
|
||||||
transition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
if let updatedIcon = updatedIcon {
|
|
||||||
strongSelf.iconNode.image = updatedIcon
|
|
||||||
}
|
|
||||||
if let image = strongSelf.iconNode.image {
|
|
||||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0 - 3.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
|
||||||
}
|
|
||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
|
||||||
}
|
|
||||||
if strongSelf.maskNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
|
||||||
var hasTopCorners = false
|
|
||||||
var hasBottomCorners = false
|
|
||||||
switch neighbors.top {
|
|
||||||
case .sameSection(false):
|
|
||||||
strongSelf.topStripeNode.isHidden = true
|
|
||||||
default:
|
|
||||||
hasTopCorners = true
|
|
||||||
strongSelf.topStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
|
|
||||||
let bottomStripeInset: CGFloat
|
|
||||||
let bottomStripeOffset: CGFloat
|
|
||||||
switch neighbors.bottom {
|
|
||||||
case .sameSection(false):
|
|
||||||
bottomStripeInset = leftInset
|
|
||||||
bottomStripeOffset = -separatorHeight
|
|
||||||
strongSelf.bottomStripeNode.isHidden = false
|
|
||||||
default:
|
|
||||||
bottomStripeInset = 0.0
|
|
||||||
bottomStripeOffset = 0.0
|
|
||||||
hasBottomCorners = true
|
|
||||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
|
||||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size))
|
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
|
||||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
|
||||||
|
|
||||||
if highlighted {
|
|
||||||
self.highlightedBackgroundNode.alpha = 1.0
|
|
||||||
if self.highlightedBackgroundNode.supernode == nil {
|
|
||||||
var anchorNode: ASDisplayNode?
|
|
||||||
if self.bottomStripeNode.supernode != nil {
|
|
||||||
anchorNode = self.bottomStripeNode
|
|
||||||
} else if self.topStripeNode.supernode != nil {
|
|
||||||
anchorNode = self.topStripeNode
|
|
||||||
} else if self.backgroundNode.supernode != nil {
|
|
||||||
anchorNode = self.backgroundNode
|
|
||||||
}
|
|
||||||
if let anchorNode = anchorNode {
|
|
||||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
|
||||||
} else {
|
|
||||||
self.addSubnode(self.highlightedBackgroundNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if self.highlightedBackgroundNode.supernode != nil {
|
|
||||||
if animated {
|
|
||||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
|
||||||
if let strongSelf = self {
|
|
||||||
if completed {
|
|
||||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
self.highlightedBackgroundNode.alpha = 0.0
|
|
||||||
} else {
|
|
||||||
self.highlightedBackgroundNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,539 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ItemListUI
|
|
||||||
import PresentationDataUtils
|
|
||||||
import CheckNode
|
|
||||||
|
|
||||||
struct CreatePollOptionItemEditing {
|
|
||||||
let editable: Bool
|
|
||||||
let hasActiveRevealControls: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
class CreatePollOptionItem: ListViewItem, ItemListItem {
|
|
||||||
let presentationData: ItemListPresentationData
|
|
||||||
let id: Int
|
|
||||||
let placeholder: String
|
|
||||||
let value: String
|
|
||||||
let isSelected: Bool?
|
|
||||||
let maxLength: Int
|
|
||||||
let editing: CreatePollOptionItemEditing
|
|
||||||
let sectionId: ItemListSectionId
|
|
||||||
let setItemIdWithRevealedOptions: (Int?, Int?) -> Void
|
|
||||||
let updated: (String, Bool) -> Void
|
|
||||||
let next: (() -> Void)?
|
|
||||||
let delete: (Bool) -> Void
|
|
||||||
let canDelete: Bool
|
|
||||||
let canMove: Bool
|
|
||||||
let focused: (Bool) -> Void
|
|
||||||
let toggleSelected: () -> Void
|
|
||||||
let tag: ItemListItemTag?
|
|
||||||
|
|
||||||
init(presentationData: ItemListPresentationData, id: Int, placeholder: String, value: String, isSelected: Bool?, maxLength: Int, editing: CreatePollOptionItemEditing, sectionId: ItemListSectionId, setItemIdWithRevealedOptions: @escaping (Int?, Int?) -> Void, updated: @escaping (String, Bool) -> Void, next: (() -> Void)?, delete: @escaping (Bool) -> Void, canDelete: Bool, canMove: Bool, focused: @escaping (Bool) -> Void, toggleSelected: @escaping () -> Void, tag: ItemListItemTag?) {
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.id = id
|
|
||||||
self.placeholder = placeholder
|
|
||||||
self.value = value
|
|
||||||
self.isSelected = isSelected
|
|
||||||
self.maxLength = maxLength
|
|
||||||
self.editing = editing
|
|
||||||
self.sectionId = sectionId
|
|
||||||
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
|
|
||||||
self.updated = updated
|
|
||||||
self.next = next
|
|
||||||
self.delete = delete
|
|
||||||
self.canDelete = canDelete
|
|
||||||
self.canMove = canMove
|
|
||||||
self.focused = focused
|
|
||||||
self.toggleSelected = toggleSelected
|
|
||||||
self.tag = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
|
||||||
async {
|
|
||||||
let node = CreatePollOptionItemNode()
|
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
|
||||||
node.insets = layout.insets
|
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(node, {
|
|
||||||
return (nil, { _ in apply(.None) })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let nodeValue = node() as? CreatePollOptionItemNode {
|
|
||||||
let makeLayout = nodeValue.asyncLayout()
|
|
||||||
|
|
||||||
async {
|
|
||||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(layout, { _ in
|
|
||||||
apply(animation)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectable: Bool = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private let titleFont = Font.regular(15.0)
|
|
||||||
|
|
||||||
class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, ItemListItemFocusableNode, ASEditableTextNodeDelegate {
|
|
||||||
private let containerNode: ASDisplayNode
|
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let topStripeNode: ASDisplayNode
|
|
||||||
private let bottomStripeNode: ASDisplayNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
|
|
||||||
private var checkNode: InteractiveCheckNode?
|
|
||||||
|
|
||||||
private let textClippingNode: ASDisplayNode
|
|
||||||
private let textNode: EditableTextNode
|
|
||||||
private let measureTextNode: TextNode
|
|
||||||
|
|
||||||
private let textLimitNode: TextNode
|
|
||||||
private let reorderControlNode: ItemListEditableReorderControlNode
|
|
||||||
|
|
||||||
private var item: CreatePollOptionItem?
|
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
|
||||||
|
|
||||||
var tag: ItemListItemTag? {
|
|
||||||
return self.item?.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
override var controlsContainer: ASDisplayNode {
|
|
||||||
return self.containerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkNodeFrame: CGRect? {
|
|
||||||
guard let _ = self.layoutParams, let checkNode = self.checkNode else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return checkNode.frame
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self.containerNode = ASDisplayNode()
|
|
||||||
self.containerNode.clipsToBounds = true
|
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.isLayerBacked = true
|
|
||||||
self.backgroundNode.backgroundColor = .white
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
|
||||||
self.topStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.bottomStripeNode = ASDisplayNode()
|
|
||||||
self.bottomStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
self.reorderControlNode = ItemListEditableReorderControlNode()
|
|
||||||
|
|
||||||
self.textClippingNode = ASDisplayNode()
|
|
||||||
self.textClippingNode.clipsToBounds = true
|
|
||||||
|
|
||||||
self.textNode = EditableTextNode()
|
|
||||||
self.measureTextNode = TextNode()
|
|
||||||
|
|
||||||
self.textLimitNode = TextNode()
|
|
||||||
self.textLimitNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
|
||||||
|
|
||||||
self.addSubnode(self.containerNode)
|
|
||||||
|
|
||||||
self.textClippingNode.addSubnode(self.textNode)
|
|
||||||
self.containerNode.addSubnode(self.textClippingNode)
|
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.reorderControlNode)
|
|
||||||
self.containerNode.addSubnode(self.textLimitNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didLoad() {
|
|
||||||
super.didLoad()
|
|
||||||
|
|
||||||
var textColor: UIColor = .black
|
|
||||||
if let item = self.item {
|
|
||||||
textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
|
||||||
}
|
|
||||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
|
||||||
self.textNode.clipsToBounds = true
|
|
||||||
self.textNode.delegate = self
|
|
||||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.item?.focused(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: true)
|
|
||||||
self.item?.focused(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
||||||
guard let item = self.item else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if text.firstIndex(of: "\n") != nil {
|
|
||||||
if text != "\n" {
|
|
||||||
let currentText = editableTextNode.attributedText?.string ?? ""
|
|
||||||
var updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
|
|
||||||
updatedText = updatedText.replacingOccurrences(of: "\n", with: " ")
|
|
||||||
if updatedText.count == 1 {
|
|
||||||
updatedText = ""
|
|
||||||
}
|
|
||||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
|
||||||
self.textNode.attributedText = updatedAttributedText
|
|
||||||
self.editableTextNodeDidUpdateText(editableTextNode)
|
|
||||||
}
|
|
||||||
if let next = item.next {
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
editableTextNode.resignFirstResponder()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.internalEditableTextNodeDidUpdateText(editableTextNode, isLosingFocus: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func internalEditableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode, isLosingFocus: Bool) {
|
|
||||||
if let item = self.item {
|
|
||||||
let text = self.textNode.attributedText ?? NSAttributedString()
|
|
||||||
|
|
||||||
var updatedText = text.string
|
|
||||||
var hadReturn = false
|
|
||||||
if updatedText.firstIndex(of: "\n") != nil {
|
|
||||||
hadReturn = true
|
|
||||||
updatedText = updatedText.replacingOccurrences(of: "\n", with: " ")
|
|
||||||
}
|
|
||||||
let updatedAttributedText = NSAttributedString(string: updatedText, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
|
||||||
if text.string != updatedAttributedText.string {
|
|
||||||
self.textNode.attributedText = updatedAttributedText
|
|
||||||
}
|
|
||||||
item.updated(updatedText, !isLosingFocus && editableTextNode.isFirstResponder())
|
|
||||||
if hadReturn {
|
|
||||||
if let next = item.next {
|
|
||||||
next()
|
|
||||||
} else if !isLosingFocus {
|
|
||||||
editableTextNode.resignFirstResponder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func editableTextNodeBackspaceWhileEmpty(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.item?.delete(editableTextNode.isFirstResponder())
|
|
||||||
}
|
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: CreatePollOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
|
||||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
|
|
||||||
let makeTextLimitLayout = TextNode.asyncLayout(self.textLimitNode)
|
|
||||||
|
|
||||||
let currentItem = self.item
|
|
||||||
|
|
||||||
return { item, params, neighbors in
|
|
||||||
var updatedTheme: PresentationTheme?
|
|
||||||
|
|
||||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
|
||||||
updatedTheme = item.presentationData.theme
|
|
||||||
}
|
|
||||||
|
|
||||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
|
||||||
|
|
||||||
let leftInset: CGFloat = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
|
|
||||||
let rightInset: CGFloat = 44.0 + params.rightInset
|
|
||||||
|
|
||||||
let textLength = item.value.count
|
|
||||||
let displayTextLimit = textLength > item.maxLength * 70 / 100
|
|
||||||
let remainingCount = item.maxLength - textLength
|
|
||||||
|
|
||||||
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
var measureText = item.value
|
|
||||||
if measureText.hasSuffix("\n") || measureText.isEmpty {
|
|
||||||
measureText += "|"
|
|
||||||
}
|
|
||||||
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
|
|
||||||
let attributedText = NSAttributedString(string: item.value, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.05, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
let textTopInset: CGFloat = 11.0
|
|
||||||
let textBottomInset: CGFloat = 11.0
|
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: textLayout.size.height + textTopInset + textBottomInset)
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
|
||||||
|
|
||||||
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: item.presentationData.theme.list.itemPlaceholderTextColor)
|
|
||||||
|
|
||||||
return (layout, { [weak self] animation in
|
|
||||||
if let strongSelf = self {
|
|
||||||
let transition: ContainedViewLayoutTransition
|
|
||||||
switch animation {
|
|
||||||
case .System:
|
|
||||||
transition = .animated(duration: 0.3, curve: .spring)
|
|
||||||
default:
|
|
||||||
transition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.item = item
|
|
||||||
strongSelf.layoutParams = params
|
|
||||||
|
|
||||||
if let _ = updatedTheme {
|
|
||||||
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
|
||||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
|
||||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
|
||||||
|
|
||||||
if strongSelf.isNodeLoaded {
|
|
||||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.presentationData.theme.list.itemPrimaryTextColor]
|
|
||||||
strongSelf.textNode.tintColor = item.presentationData.theme.list.itemAccentColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let revealOffset = strongSelf.revealOffset
|
|
||||||
|
|
||||||
let capitalizationType: UITextAutocapitalizationType
|
|
||||||
let autocorrectionType: UITextAutocorrectionType
|
|
||||||
let keyboardType: UIKeyboardType
|
|
||||||
|
|
||||||
capitalizationType = .sentences
|
|
||||||
autocorrectionType = .default
|
|
||||||
keyboardType = UIKeyboardType.default
|
|
||||||
|
|
||||||
let _ = textApply()
|
|
||||||
if let currentText = strongSelf.textNode.attributedText {
|
|
||||||
if currentText.string != attributedText.string || updatedTheme != nil {
|
|
||||||
strongSelf.textNode.attributedText = attributedText
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
strongSelf.textNode.attributedText = attributedText
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.textNode.keyboardType != keyboardType {
|
|
||||||
strongSelf.textNode.keyboardType = keyboardType
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.autocapitalizationType != capitalizationType {
|
|
||||||
strongSelf.textNode.autocapitalizationType = capitalizationType
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.autocorrectionType != autocorrectionType {
|
|
||||||
strongSelf.textNode.autocorrectionType = autocorrectionType
|
|
||||||
}
|
|
||||||
let returnKeyType: UIReturnKeyType
|
|
||||||
if let _ = item.next {
|
|
||||||
returnKeyType = .next
|
|
||||||
} else {
|
|
||||||
returnKeyType = .done
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.returnKeyType != returnKeyType {
|
|
||||||
strongSelf.textNode.returnKeyType = returnKeyType
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
|
|
||||||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
|
||||||
|
|
||||||
let checkSize = CGSize(width: 22.0, height: 22.0)
|
|
||||||
let checkFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + 16.0, y: floor((layout.contentSize.height - checkSize.height) / 2.0)), size: checkSize)
|
|
||||||
if let isSelected = item.isSelected {
|
|
||||||
if let checkNode = strongSelf.checkNode {
|
|
||||||
transition.updateFrame(node: checkNode, frame: checkFrame)
|
|
||||||
checkNode.setSelected(isSelected, animated: true)
|
|
||||||
} else {
|
|
||||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: item.presentationData.theme.list.itemSwitchColors.positiveColor, strokeColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: item.presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false))
|
|
||||||
checkNode.setSelected(isSelected, animated: false)
|
|
||||||
checkNode.valueChanged = { [weak self] value in
|
|
||||||
self?.item?.toggleSelected()
|
|
||||||
}
|
|
||||||
strongSelf.checkNode = checkNode
|
|
||||||
strongSelf.containerNode.addSubnode(checkNode)
|
|
||||||
checkNode.frame = checkFrame
|
|
||||||
transition.animatePositionAdditive(node: checkNode, offset: CGPoint(x: -checkFrame.maxX, y: 0.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let checkNode = strongSelf.checkNode {
|
|
||||||
transition.updateAlpha(node: checkNode, alpha: strongSelf.textNode.textView.text.isEmpty && item.placeholder == item.presentationData.strings.CreatePoll_AddOption ? 0.0 : 1.0)
|
|
||||||
}
|
|
||||||
} else if let checkNode = strongSelf.checkNode {
|
|
||||||
strongSelf.checkNode = nil
|
|
||||||
transition.updateFrame(node: checkNode, frame: checkFrame.offsetBy(dx: -checkFrame.maxX, dy: 0.0), completion: { [weak checkNode] _ in
|
|
||||||
checkNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.textClippingNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height)))
|
|
||||||
transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0)))
|
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode == nil {
|
|
||||||
strongSelf.containerNode.insertSubnode(strongSelf.topStripeNode, at: 1)
|
|
||||||
}
|
|
||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
|
||||||
strongSelf.containerNode.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
|
||||||
}
|
|
||||||
if strongSelf.maskNode.supernode == nil {
|
|
||||||
strongSelf.containerNode.insertSubnode(strongSelf.maskNode, at: 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
let bottomStripeWasHidden = strongSelf.bottomStripeNode.isHidden
|
|
||||||
|
|
||||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
|
||||||
var hasTopCorners = false
|
|
||||||
var hasBottomCorners = false
|
|
||||||
switch neighbors.top {
|
|
||||||
case .sameSection(false):
|
|
||||||
strongSelf.topStripeNode.isHidden = true
|
|
||||||
default:
|
|
||||||
hasTopCorners = true
|
|
||||||
strongSelf.topStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
let bottomStripeInset: CGFloat
|
|
||||||
switch neighbors.bottom {
|
|
||||||
case .sameSection(false):
|
|
||||||
bottomStripeInset = leftInset
|
|
||||||
strongSelf.bottomStripeNode.isHidden = false
|
|
||||||
default:
|
|
||||||
bottomStripeInset = 0.0
|
|
||||||
hasBottomCorners = true
|
|
||||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
|
||||||
|
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
|
||||||
if strongSelf.animationForKey("apparentHeight") == nil {
|
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
let previousX = strongSelf.bottomStripeNode.frame.minX
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
|
||||||
if !bottomStripeWasHidden {
|
|
||||||
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let previousX = strongSelf.bottomStripeNode.frame.minX
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: strongSelf.bottomStripeNode.frame.minY), size: CGSize(width: layout.contentSize.width, height: separatorHeight))
|
|
||||||
if !bottomStripeWasHidden {
|
|
||||||
transition.animatePositionAdditive(node: strongSelf.bottomStripeNode, offset: CGPoint(x: previousX - strongSelf.bottomStripeNode.frame.minX, y: 0.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = reorderSizeAndApply.1(layout.contentSize.height, displayTextLimit, transition)
|
|
||||||
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width + revealOffset - params.rightInset - reorderSizeAndApply.0 + (!item.canMove ? 44.0 + params.rightInset : 0.0), y: 0.0), size: CGSize(width: reorderSizeAndApply.0, height: layout.contentSize.height))
|
|
||||||
transition.updateFrameAdditive(node: strongSelf.reorderControlNode, frame: reorderControlFrame)
|
|
||||||
strongSelf.reorderControlNode.isUserInteractionEnabled = item.canMove
|
|
||||||
|
|
||||||
let _ = textLimitApply()
|
|
||||||
strongSelf.textLimitNode.frame = CGRect(origin: CGPoint(x: reorderControlFrame.minX + floor((reorderControlFrame.width - textLimitLayout.size.width) / 2.0) - 4.0 - UIScreenPixel, y: max(floor(reorderControlFrame.midY + 2.0), layout.contentSize.height - 15.0 - textLimitLayout.size.height)), size: textLimitLayout.size)
|
|
||||||
strongSelf.textLimitNode.isHidden = !displayTextLimit
|
|
||||||
|
|
||||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
|
||||||
|
|
||||||
strongSelf.setRevealOptions((left: [], right: item.canDelete ? [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] : []))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.updateRevealOffset(offset: offset, transition: transition)
|
|
||||||
|
|
||||||
guard let params = self.layoutParams, let item = self.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let revealOffset = offset
|
|
||||||
|
|
||||||
let leftInset: CGFloat
|
|
||||||
leftInset = params.leftInset + (item.isSelected != nil ? 60.0 : 16.0)
|
|
||||||
|
|
||||||
if let checkNode = self.checkNode {
|
|
||||||
var checkNodeFrame = checkNode.frame
|
|
||||||
checkNodeFrame.origin.x = params.leftInset + 11.0 + revealOffset
|
|
||||||
transition.updateFrame(node: checkNode, frame: checkNodeFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reorderFrame = self.reorderControlNode.frame
|
|
||||||
reorderFrame.origin.x = params.width + revealOffset - params.rightInset - reorderFrame.width
|
|
||||||
transition.updateFrame(node: self.reorderControlNode, frame: reorderFrame)
|
|
||||||
|
|
||||||
var textClippingNodeFrame = self.textClippingNode.frame
|
|
||||||
textClippingNodeFrame.origin.x = revealOffset + leftInset
|
|
||||||
transition.updateFrame(node: self.textClippingNode, frame: textClippingNodeFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
|
||||||
self.layer.allowsGroupOpacity = true
|
|
||||||
self.updateRevealOffsetInternal(offset: -self.bounds.width - 74.0, transition: .animated(duration: 0.2, curve: .spring), completion: { [weak self] in
|
|
||||||
self?.layer.allowsGroupOpacity = false
|
|
||||||
})
|
|
||||||
self.item?.delete(self.textNode.isFirstResponder())
|
|
||||||
}
|
|
||||||
|
|
||||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
|
||||||
//self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
|
||||||
//self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func focus() {
|
|
||||||
self.textNode.becomeFirstResponder()
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectAll() {
|
|
||||||
self.textNode.textView.selectAll(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func isReorderable(at point: CGPoint) -> Bool {
|
|
||||||
if self.reorderControlNode.frame.contains(point), !self.reorderControlNode.isHidden, !self.isDisplayingRevealedOptions {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
|
||||||
super.animateFrameTransition(progress, currentValue)
|
|
||||||
|
|
||||||
var separatorFrame = self.bottomStripeNode.frame
|
|
||||||
separatorFrame.origin.y = currentValue - UIScreenPixel
|
|
||||||
self.bottomStripeNode.frame = separatorFrame
|
|
||||||
|
|
||||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.containerNode.bounds.width, height: currentValue))
|
|
||||||
|
|
||||||
let insets = self.insets
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
guard let params = self.layoutParams else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: self.containerNode.bounds.width, height: currentValue + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,683 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ItemListUI
|
|
||||||
import TextFormat
|
|
||||||
import ObjCRuntimeUtils
|
|
||||||
import TextInputMenu
|
|
||||||
import AccountContext
|
|
||||||
|
|
||||||
public enum CreatePollTextInputItemTextLimitMode {
|
|
||||||
case characters
|
|
||||||
case bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct CreatePollTextInputItemTextLimit {
|
|
||||||
public let value: Int
|
|
||||||
public let display: Bool
|
|
||||||
public let mode: CreatePollTextInputItemTextLimitMode
|
|
||||||
|
|
||||||
public init(value: Int, display: Bool, mode: CreatePollTextInputItemTextLimitMode = .characters) {
|
|
||||||
self.value = value
|
|
||||||
self.display = display
|
|
||||||
self.mode = mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ItemListMultilineInputInlineAction {
|
|
||||||
public let icon: UIImage
|
|
||||||
public let action: (() -> Void)?
|
|
||||||
|
|
||||||
public init(icon: UIImage, action: (() -> Void)?) {
|
|
||||||
self.icon = icon
|
|
||||||
self.action = action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CreatePollTextInputItem: ListViewItem, ItemListItem {
|
|
||||||
let context: AccountContext
|
|
||||||
let presentationData: ItemListPresentationData
|
|
||||||
let text: NSAttributedString
|
|
||||||
let placeholder: String
|
|
||||||
public let sectionId: ItemListSectionId
|
|
||||||
let style: ItemListStyle
|
|
||||||
let capitalization: Bool
|
|
||||||
let autocorrection: Bool
|
|
||||||
let returnKeyType: UIReturnKeyType
|
|
||||||
let action: (() -> Void)?
|
|
||||||
let textUpdated: (NSAttributedString) -> Void
|
|
||||||
let shouldUpdateText: (String) -> Bool
|
|
||||||
let processPaste: ((String) -> Void)?
|
|
||||||
let updatedFocus: ((Bool) -> Void)?
|
|
||||||
let maxLength: CreatePollTextInputItemTextLimit?
|
|
||||||
let minimalHeight: CGFloat?
|
|
||||||
let inlineAction: ItemListMultilineInputInlineAction?
|
|
||||||
public let tag: ItemListItemTag?
|
|
||||||
|
|
||||||
public init(context: AccountContext, presentationData: ItemListPresentationData, text: NSAttributedString, placeholder: String, maxLength: CreatePollTextInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (NSAttributedString) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil) {
|
|
||||||
self.context = context
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.text = text
|
|
||||||
self.placeholder = placeholder
|
|
||||||
self.maxLength = maxLength
|
|
||||||
self.sectionId = sectionId
|
|
||||||
self.style = style
|
|
||||||
self.capitalization = capitalization
|
|
||||||
self.autocorrection = autocorrection
|
|
||||||
self.returnKeyType = returnKeyType
|
|
||||||
self.minimalHeight = minimalHeight
|
|
||||||
self.textUpdated = textUpdated
|
|
||||||
self.shouldUpdateText = shouldUpdateText
|
|
||||||
self.processPaste = processPaste
|
|
||||||
self.updatedFocus = updatedFocus
|
|
||||||
self.tag = tag
|
|
||||||
self.action = action
|
|
||||||
self.inlineAction = inlineAction
|
|
||||||
}
|
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
|
||||||
async {
|
|
||||||
let node = CreatePollTextInputItemNode()
|
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
|
||||||
node.insets = layout.insets
|
|
||||||
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(node, {
|
|
||||||
return (nil, { _ in apply() })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let nodeValue = node() as? CreatePollTextInputItemNode {
|
|
||||||
let makeLayout = nodeValue.asyncLayout()
|
|
||||||
|
|
||||||
async {
|
|
||||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
completion(layout, { _ in
|
|
||||||
apply()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDelegate, ItemListItemNode, ItemListItemFocusableNode {
|
|
||||||
private let backgroundNode: ASDisplayNode
|
|
||||||
private let topStripeNode: ASDisplayNode
|
|
||||||
private let bottomStripeNode: ASDisplayNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
|
|
||||||
private let textClippingNode: ASDisplayNode
|
|
||||||
private let textNode: EditableTextNode
|
|
||||||
private let measureTextNode: TextNode
|
|
||||||
|
|
||||||
private let limitTextNode: TextNode
|
|
||||||
private var inlineActionButtonNode: HighlightableButtonNode?
|
|
||||||
|
|
||||||
private var item: CreatePollTextInputItem?
|
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
|
||||||
|
|
||||||
private let inputMenu = TextInputMenu()
|
|
||||||
|
|
||||||
public var tag: ItemListItemTag? {
|
|
||||||
return self.item?.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
|
||||||
self.backgroundNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.topStripeNode = ASDisplayNode()
|
|
||||||
self.topStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.bottomStripeNode = ASDisplayNode()
|
|
||||||
self.bottomStripeNode.isLayerBacked = true
|
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
self.textClippingNode = ASDisplayNode()
|
|
||||||
self.textClippingNode.clipsToBounds = true
|
|
||||||
|
|
||||||
self.textNode = EditableTextNode()
|
|
||||||
self.measureTextNode = TextNode()
|
|
||||||
|
|
||||||
self.limitTextNode = TextNode()
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
|
||||||
|
|
||||||
self.textClippingNode.addSubnode(self.textNode)
|
|
||||||
self.addSubnode(self.textClippingNode)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func didLoad() {
|
|
||||||
super.didLoad()
|
|
||||||
|
|
||||||
var textColor: UIColor = .black
|
|
||||||
if let item = self.item {
|
|
||||||
textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
|
||||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
|
||||||
} else {
|
|
||||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
|
||||||
}
|
|
||||||
self.textNode.clipsToBounds = true
|
|
||||||
self.textNode.delegate = self
|
|
||||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func asyncLayout() -> (_ item: CreatePollTextInputItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
|
|
||||||
let makeLimitTextLayout = TextNode.asyncLayout(self.limitTextNode)
|
|
||||||
|
|
||||||
let currentItem = self.item
|
|
||||||
|
|
||||||
return { item, params, neighbors in
|
|
||||||
var updatedTheme: PresentationTheme?
|
|
||||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
|
||||||
updatedTheme = item.presentationData.theme
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemBackgroundColor: UIColor
|
|
||||||
let itemSeparatorColor: UIColor
|
|
||||||
|
|
||||||
let leftInset = 16.0 + params.rightInset
|
|
||||||
switch item.style {
|
|
||||||
case .plain:
|
|
||||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
|
||||||
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
|
||||||
case .blocks:
|
|
||||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
|
||||||
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
|
||||||
}
|
|
||||||
|
|
||||||
var limitTextString: NSAttributedString?
|
|
||||||
var rightInset: CGFloat = params.rightInset
|
|
||||||
|
|
||||||
if let maxLength = item.maxLength, maxLength.display {
|
|
||||||
let textLength: Int
|
|
||||||
switch maxLength.mode {
|
|
||||||
case .characters:
|
|
||||||
textLength = item.text.string.count
|
|
||||||
case .bytes:
|
|
||||||
textLength = item.text.string.data(using: .utf8, allowLossyConversion: true)?.count ?? 0
|
|
||||||
}
|
|
||||||
let displayTextLimit = textLength > maxLength.value * 70 / 100
|
|
||||||
let remainingCount = maxLength.value - textLength
|
|
||||||
if displayTextLimit {
|
|
||||||
limitTextString = NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
rightInset += 30.0 + 4.0
|
|
||||||
}
|
|
||||||
|
|
||||||
let (limitTextLayout, limitTextApply) = makeLimitTextLayout(TextNodeLayoutArguments(attributedString: limitTextString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
if limitTextLayout.size.width > 30.0 {
|
|
||||||
rightInset += 30.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if let inlineAction = item.inlineAction {
|
|
||||||
rightInset += inlineAction.icon.size.width + 8.0
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemText = textAttributedStringForStateText(context: item.context, stateText: item.text, fontSize: 17.0, textColor: item.presentationData.theme.chat.inputPanel.primaryTextColor, accentTextColor: item.presentationData.theme.chat.inputPanel.panelControlAccentColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
let measureText = NSMutableAttributedString(attributedString: itemText)
|
|
||||||
let measureRawString = measureText.string
|
|
||||||
if measureRawString.hasSuffix("\n") || measureRawString.isEmpty {
|
|
||||||
measureText.append(NSAttributedString(string: "|", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: .black))
|
|
||||||
}
|
|
||||||
let attributedText = itemText
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: measureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
|
|
||||||
let textTopInset: CGFloat = 11.0
|
|
||||||
let textBottomInset: CGFloat = 11.0
|
|
||||||
|
|
||||||
var contentHeight: CGFloat = textLayout.size.height + textTopInset + textBottomInset
|
|
||||||
if let minimalHeight = item.minimalHeight {
|
|
||||||
contentHeight = max(minimalHeight, contentHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: contentHeight)
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
|
||||||
let layoutSize = layout.size
|
|
||||||
|
|
||||||
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemPlaceholderTextColor)
|
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.item = item
|
|
||||||
strongSelf.layoutParams = params
|
|
||||||
|
|
||||||
if let _ = updatedTheme {
|
|
||||||
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
|
||||||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
|
||||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
|
||||||
|
|
||||||
if strongSelf.isNodeLoaded {
|
|
||||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), NSAttributedString.Key.foregroundColor.rawValue: item.presentationData.theme.list.itemPrimaryTextColor]
|
|
||||||
strongSelf.textNode.tintColor = item.presentationData.theme.list.itemAccentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
if let inlineAction = item.inlineAction {
|
|
||||||
strongSelf.inlineActionButtonNode?.setImage(generateTintedImage(image: inlineAction.icon, color: item.presentationData.theme.list.itemAccentColor), for: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.inputMenu.updateStrings(item.presentationData.strings)
|
|
||||||
}
|
|
||||||
|
|
||||||
let capitalizationType: UITextAutocapitalizationType = item.capitalization ? .sentences : .none
|
|
||||||
let autocorrectionType: UITextAutocorrectionType = item.autocorrection ? .default : .no
|
|
||||||
|
|
||||||
if strongSelf.textNode.textView.autocapitalizationType != capitalizationType {
|
|
||||||
strongSelf.textNode.textView.autocapitalizationType = capitalizationType
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.textView.autocorrectionType != autocorrectionType {
|
|
||||||
strongSelf.textNode.textView.autocorrectionType = autocorrectionType
|
|
||||||
}
|
|
||||||
if strongSelf.textNode.textView.returnKeyType != item.returnKeyType {
|
|
||||||
strongSelf.textNode.textView.returnKeyType = item.returnKeyType
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = textApply()
|
|
||||||
if let currentText = strongSelf.textNode.attributedText {
|
|
||||||
if currentText.string != attributedText.string || updatedTheme != nil {
|
|
||||||
strongSelf.textNode.attributedText = attributedText
|
|
||||||
refreshGenericTextInputAttributes(context: item.context, textView: strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
strongSelf.textNode.attributedText = attributedText
|
|
||||||
refreshGenericTextInputAttributes(context: item.context, textView: strongSelf.textNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
|
||||||
}
|
|
||||||
if strongSelf.topStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
|
||||||
}
|
|
||||||
if strongSelf.bottomStripeNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
|
||||||
}
|
|
||||||
if strongSelf.maskNode.supernode == nil {
|
|
||||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
|
||||||
var hasTopCorners = false
|
|
||||||
var hasBottomCorners = false
|
|
||||||
switch neighbors.top {
|
|
||||||
case .sameSection(false):
|
|
||||||
strongSelf.topStripeNode.isHidden = true
|
|
||||||
default:
|
|
||||||
hasTopCorners = true
|
|
||||||
strongSelf.topStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
let bottomStripeInset: CGFloat
|
|
||||||
switch neighbors.bottom {
|
|
||||||
case .sameSection(false):
|
|
||||||
bottomStripeInset = leftInset
|
|
||||||
strongSelf.bottomStripeNode.isHidden = false
|
|
||||||
default:
|
|
||||||
bottomStripeInset = 0.0
|
|
||||||
hasBottomCorners = true
|
|
||||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
|
||||||
|
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
|
||||||
|
|
||||||
if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
|
|
||||||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.textNode.keyboardAppearance = item.presentationData.theme.rootController.keyboardColor.keyboardAppearance
|
|
||||||
|
|
||||||
if strongSelf.animationForKey("apparentHeight") == nil {
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
|
||||||
}
|
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: textLayout.size.height + 1.0))
|
|
||||||
|
|
||||||
let _ = limitTextApply()
|
|
||||||
strongSelf.limitTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 16.0 - limitTextLayout.size.width, y: layout.contentSize.height - 15.0 - limitTextLayout.size.height), size: limitTextLayout.size)
|
|
||||||
if limitTextString != nil {
|
|
||||||
if strongSelf.limitTextNode.supernode == nil {
|
|
||||||
strongSelf.addSubnode(strongSelf.limitTextNode)
|
|
||||||
}
|
|
||||||
} else if strongSelf.limitTextNode.supernode != nil {
|
|
||||||
strongSelf.limitTextNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let inlineAction = item.inlineAction {
|
|
||||||
let inlineActionButtonNode: HighlightableButtonNode
|
|
||||||
if let currentInlineActionButtonNode = strongSelf.inlineActionButtonNode {
|
|
||||||
inlineActionButtonNode = currentInlineActionButtonNode
|
|
||||||
} else {
|
|
||||||
inlineActionButtonNode = HighlightableButtonNode()
|
|
||||||
inlineActionButtonNode.setImage(generateTintedImage(image: inlineAction.icon, color: item.presentationData.theme.list.itemAccentColor), for: .normal)
|
|
||||||
inlineActionButtonNode.addTarget(strongSelf, action: #selector(strongSelf.inlineActionPressed), forControlEvents: .touchUpInside)
|
|
||||||
strongSelf.addSubnode(inlineActionButtonNode)
|
|
||||||
strongSelf.inlineActionButtonNode = inlineActionButtonNode
|
|
||||||
}
|
|
||||||
inlineActionButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - inlineAction.icon.size.width - 11.0, y: 7.0), size: inlineAction.icon.size)
|
|
||||||
} else if let inlineActionButtonNode = strongSelf.inlineActionButtonNode {
|
|
||||||
inlineActionButtonNode.removeFromSupernode()
|
|
||||||
strongSelf.inlineActionButtonNode = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
|
||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
|
||||||
super.animateFrameTransition(progress, currentValue)
|
|
||||||
|
|
||||||
guard let params = self.layoutParams else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
let insets = self.insets
|
|
||||||
let contentSize = CGSize(width: params.width, height: max(1.0, currentValue - insets.top - insets.bottom))
|
|
||||||
|
|
||||||
let leftInset = 16.0 + params.leftInset
|
|
||||||
let textTopInset: CGFloat = 11.0
|
|
||||||
let textBottomInset: CGFloat = 11.0
|
|
||||||
|
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
|
||||||
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
|
||||||
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: self.bottomStripeNode.frame.minX, y: contentSize.height), size: CGSize(width: self.bottomStripeNode.frame.size.width, height: separatorHeight))
|
|
||||||
|
|
||||||
self.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: max(0.0, params.width - leftInset - params.rightInset), height: max(0.0, contentSize.height - textTopInset - textBottomInset)))
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.item?.updatedFocus?(true)
|
|
||||||
self.inputMenu.activate()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
self.item?.updatedFocus?(false)
|
|
||||||
self.inputMenu.deactivate()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeTarget(forAction action: Selector) -> ASEditableTextNodeTargetForAction? {
|
|
||||||
if action == makeSelectorFromString("_showTextStyleOptions:") {
|
|
||||||
if #available(iOS 16.0, *) {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: nil)
|
|
||||||
} else {
|
|
||||||
if case .general = self.inputMenu.state {
|
|
||||||
if self.textNode.attributedText == nil || self.textNode.attributedText!.length == 0 || self.textNode.selectedRange.length == 0 {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: nil)
|
|
||||||
}
|
|
||||||
return ASEditableTextNodeTargetForAction(target: self)
|
|
||||||
} else {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if action == #selector(self.formatAttributesBold(_:)) || action == #selector(self.formatAttributesItalic(_:)) || action == #selector(self.formatAttributesMonospace(_:)) || action == #selector(self.formatAttributesLink(_:)) || action == #selector(self.formatAttributesStrikethrough(_:)) || action == #selector(self.formatAttributesUnderline(_:)) {
|
|
||||||
if case .format = self.inputMenu.state {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: self)
|
|
||||||
} else {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if case .format = self.inputMenu.state {
|
|
||||||
return ASEditableTextNodeTargetForAction(target: nil)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func _showTextStyleOptions(_ sender: Any) {
|
|
||||||
self.inputMenu.format(view: self.textNode.view, rect: self.textNode.selectionRect.offsetBy(dx: 0.0, dy: -self.textNode.textView.contentOffset.y).insetBy(dx: 0.0, dy: -1.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 16.0, *)
|
|
||||||
public func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
|
|
||||||
var actions = suggestedActions
|
|
||||||
|
|
||||||
if editableTextNode.attributedText == nil || editableTextNode.attributedText!.length == 0 || editableTextNode.selectedRange.length == 0 {
|
|
||||||
|
|
||||||
} else if let strings = self.item?.presentationData.strings {
|
|
||||||
let children: [UIAction] = [
|
|
||||||
UIAction(title: strings.TextFormat_Bold, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesBold(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Italic, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesItalic(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Monospace, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesMonospace(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Link, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesLink(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Strikethrough, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesStrikethrough(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Underline, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesUnderline(strongSelf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UIAction(title: strings.TextFormat_Spoiler, image: nil) { [weak self] (action) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.formatAttributesSpoiler(strongSelf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
let formatMenu = UIMenu(title: strings.TextFormat_Format, image: nil, children: children)
|
|
||||||
actions.insert(formatMenu, at: 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
return UIMenu(children: actions)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesBold(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.bold, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesItalic(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.italic, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesMonospace(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.monospace, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesLink(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
//self.interfaceInteraction?.openLinkEditing()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesStrikethrough(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.strikethrough, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesUnderline(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.underline, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesSpoiler(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.spoiler, value: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesQuote(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func formatAttributesCodeBlock(_ sender: Any) {
|
|
||||||
self.inputMenu.back()
|
|
||||||
if let item = self.item {
|
|
||||||
chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
||||||
if let item = self.item {
|
|
||||||
if text.count > 1, let processPaste = item.processPaste {
|
|
||||||
processPaste(text)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if let action = item.action, text == "\n" {
|
|
||||||
action()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let newText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
|
||||||
if !item.shouldUpdateText(newText) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
|
||||||
if let item = self.item {
|
|
||||||
if let _ = self.textNode.attributedText {
|
|
||||||
refreshGenericTextInputAttributes(context: item.context, textView: editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
let updatedText = stateAttributedStringForText(self.textNode.attributedText!)
|
|
||||||
item.textUpdated(updatedText)
|
|
||||||
} else {
|
|
||||||
item.textUpdated(NSAttributedString(string: ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
|
|
||||||
if let _ = self.item {
|
|
||||||
let text: String? = UIPasteboard.general.string
|
|
||||||
if let _ = text {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
public func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
|
|
||||||
/*if !dueToEditing && !self.updatingInputState {
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if let item = self.item {
|
|
||||||
if case .format = self.inputMenu.state {
|
|
||||||
self.inputMenu.deactivate()
|
|
||||||
UIMenuController.shared.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshChatTextInputTypingAttributes(editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0)
|
|
||||||
refreshGenericTextInputAttributes(context: item.context, textView: editableTextNode.textView, theme: item.presentationData.theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func focus() {
|
|
||||||
if !self.textNode.textView.isFirstResponder {
|
|
||||||
self.textNode.textView.becomeFirstResponder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func selectAll() {
|
|
||||||
self.textNode.textView.selectAll(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func animateError() {
|
|
||||||
self.textNode.layer.addShakeAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func inlineActionPressed() {
|
|
||||||
if let action = self.item?.inlineAction?.action {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func chatTextInputAddFormattingAttribute(item: CreatePollTextInputItem, textNode: EditableTextNode, theme: PresentationTheme, attribute: NSAttributedString.Key, value: Any?) {
|
|
||||||
if let currentText = textNode.attributedText, textNode.selectedRange.length > 0 {
|
|
||||||
let nsRange = NSRange(location: textNode.selectedRange.location, length: textNode.selectedRange.length)
|
|
||||||
var addAttribute = true
|
|
||||||
var attributesToRemove: [NSAttributedString.Key] = []
|
|
||||||
currentText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
|
|
||||||
for (key, _) in attributes {
|
|
||||||
if key == attribute && range == nsRange {
|
|
||||||
addAttribute = false
|
|
||||||
attributesToRemove.append(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = NSMutableAttributedString(attributedString: currentText)
|
|
||||||
for attribute in attributesToRemove {
|
|
||||||
result.removeAttribute(attribute, range: nsRange)
|
|
||||||
}
|
|
||||||
if addAttribute {
|
|
||||||
result.addAttribute(attribute, value: true as Bool, range: nsRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
textNode.attributedText = result
|
|
||||||
textNode.selectedRange = nsRange
|
|
||||||
|
|
||||||
refreshChatTextInputTypingAttributes(textNode.textView, theme: theme, baseFontSize: 17.0)
|
|
||||||
refreshGenericTextInputAttributes(context: item.context, textView: textNode.textView, theme: theme, baseFontSize: 17.0, availableEmojis: Set(), emojiViewProvider: nil, makeCollapsedQuoteAttachment: nil)
|
|
||||||
|
|
||||||
let updatedText = stateAttributedStringForText(textNode.attributedText!)
|
|
||||||
item.textUpdated(updatedText)
|
|
||||||
}
|
|
||||||
}
|
|
@ -60,9 +60,12 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
"//submodules/Components/BalancedTextComponent",
|
"//submodules/Components/BalancedTextComponent",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/ToastComponent",
|
||||||
|
"//submodules/SemanticStatusNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -33,6 +33,9 @@ import RasterizedCompositionComponent
|
|||||||
import BadgeComponent
|
import BadgeComponent
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
|
import ToastComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
|
||||||
public enum UniversalVideoGalleryItemContentInfo {
|
public enum UniversalVideoGalleryItemContentInfo {
|
||||||
case message(Message, Int?)
|
case message(Message, Int?)
|
||||||
@ -1390,7 +1393,93 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
if dismiss {
|
if dismiss {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.adDisposable == nil, let contentInfo = self.item?.contentInfo, case let .message(message, _) = contentInfo {
|
||||||
|
let adContext = self.context.engine.messages.adMessages(peerId: message.id.peerId, messageId: message.id)
|
||||||
|
self.adContext = adContext
|
||||||
|
self.adDisposable = (adContext.state
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if let message = state.messages.first {
|
||||||
|
Queue.mainQueue().after(2.0, {
|
||||||
|
self.adMessage = message
|
||||||
|
if let validLayout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if let adMessage = self.adMessage {
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let title = adMessage.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
|
||||||
|
|
||||||
|
let adSize = self.adView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
ToastContentComponent(
|
||||||
|
icon: AnyComponent(
|
||||||
|
BundleIconComponent(name: "Components/AdMock", tintColor: nil, maxSize: CGSize(width: 30.0, height: 30.0))
|
||||||
|
),
|
||||||
|
content: AnyComponent(
|
||||||
|
HStack([
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||||
|
VStack([
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||||
|
HStack([
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white))))),
|
||||||
|
AnyComponentWithIdentity(id: 1, component: AnyComponent(Image(image: PresentationResourcesChatList.searchAdIcon(presentationData.theme, strings: presentationData.strings), size: CGSize(width: 31.0, height: 15.0))))
|
||||||
|
], spacing: 5.0)
|
||||||
|
)),
|
||||||
|
AnyComponentWithIdentity(id: 1, component: AnyComponent(
|
||||||
|
MultilineTextComponent(text: .plain(NSAttributedString(string: adMessage.text, font: Font.regular(14.0), textColor: .white)))
|
||||||
|
))
|
||||||
|
], alignment: .left, spacing: 3.0, fillWidth: false)
|
||||||
|
)),
|
||||||
|
AnyComponentWithIdentity(id: 1, component: AnyComponent(
|
||||||
|
AdRemainingProgressComponent(action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.adMessage = nil
|
||||||
|
if let validLayout = self.validLayout {
|
||||||
|
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
))
|
||||||
|
], spacing: 16.0, alignment: .alternatingLeftRight)
|
||||||
|
), action: { [weak self] in
|
||||||
|
if let self, let item = self.item, let ad = adMessage.adAttribute {
|
||||||
|
item.performAction(.url(url: ad.url, concealed: false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: layout.size.width - sideInset * 2.0, height: 70.0)
|
||||||
|
)
|
||||||
|
if let adView = self.adView.view {
|
||||||
|
if adView.superview == nil {
|
||||||
|
self.view.addSubview(adView)
|
||||||
|
|
||||||
|
adView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
adView.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
}
|
||||||
|
adView.frame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - adSize.height - 145.0), size: adSize)
|
||||||
|
}
|
||||||
|
} else if let adView = self.adView.view {
|
||||||
|
adView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
adView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var adView = ComponentView<Empty>()
|
||||||
|
private var adContext: AdMessagesHistoryContext?
|
||||||
|
private var adDisposable: Disposable?
|
||||||
|
private var adMessage: Message?
|
||||||
|
|
||||||
func setupItem(_ item: UniversalVideoGalleryItem) {
|
func setupItem(_ item: UniversalVideoGalleryItem) {
|
||||||
if self.item?.content.id != item.content.id {
|
if self.item?.content.id != item.content.id {
|
||||||
|
@ -1118,6 +1118,26 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
availableItems[.todo] = DemoPagerComponent.Item(
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: PremiumDemoScreen.Subject.todo,
|
||||||
|
component: AnyComponent(
|
||||||
|
PageComponent(
|
||||||
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
|
context: component.context,
|
||||||
|
position: .top,
|
||||||
|
videoFile: configuration.videos["todo"],
|
||||||
|
decoration: .badgeStars
|
||||||
|
)),
|
||||||
|
title: "To-Do Lists",
|
||||||
|
text: "Plan, assign and complete tasks – seamlessly and efficiently.",
|
||||||
|
textColor: textColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
let index: Int = 0
|
let index: Int = 0
|
||||||
var items: [DemoPagerComponent.Item] = []
|
var items: [DemoPagerComponent.Item] = []
|
||||||
if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) {
|
if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) {
|
||||||
@ -1216,6 +1236,9 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
text = strings.Premium_MessageEffectsInfo
|
text = strings.Premium_MessageEffectsInfo
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
text = strings.Premium_PaidMessagesInfo
|
text = strings.Premium_PaidMessagesInfo
|
||||||
|
case .todo:
|
||||||
|
//TODO:localize
|
||||||
|
text = "Plan, assign and complete tasks – seamlessly and efficiently."
|
||||||
default:
|
default:
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
@ -1302,6 +1325,8 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
buttonAnimationName = "premium_unlock"
|
buttonAnimationName = "premium_unlock"
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
buttonText = strings.Premium_PaidMessages_Proceed
|
buttonText = strings.Premium_PaidMessages_Proceed
|
||||||
|
case .todo:
|
||||||
|
buttonText = strings.Premium_PaidMessages_Proceed
|
||||||
default:
|
default:
|
||||||
buttonText = strings.Common_OK
|
buttonText = strings.Common_OK
|
||||||
}
|
}
|
||||||
@ -1492,6 +1517,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
|||||||
case folderTags
|
case folderTags
|
||||||
case messageEffects
|
case messageEffects
|
||||||
case paidMessages
|
case paidMessages
|
||||||
|
case todo
|
||||||
|
|
||||||
case businessLocation
|
case businessLocation
|
||||||
case businessHours
|
case businessHours
|
||||||
@ -1552,6 +1578,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
|||||||
return .messageEffects
|
return .messageEffects
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return .paidMessages
|
return .paidMessages
|
||||||
|
case .todo:
|
||||||
|
return .todo
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return .businessLocation
|
return .businessLocation
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
|
@ -309,6 +309,12 @@ public enum PremiumSource: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .todo:
|
||||||
|
if case .todo = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .auth(lhsPrice):
|
case let .auth(lhsPrice):
|
||||||
if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice {
|
if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice {
|
||||||
return true
|
return true
|
||||||
@ -363,6 +369,7 @@ public enum PremiumSource: Equatable {
|
|||||||
case folderTags
|
case folderTags
|
||||||
case messageEffects
|
case messageEffects
|
||||||
case paidMessages
|
case paidMessages
|
||||||
|
case todo
|
||||||
case auth(String)
|
case auth(String)
|
||||||
|
|
||||||
var identifier: String? {
|
var identifier: String? {
|
||||||
@ -459,6 +466,8 @@ public enum PremiumSource: Equatable {
|
|||||||
return "effects"
|
return "effects"
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return "paid_messages"
|
return "paid_messages"
|
||||||
|
case .todo:
|
||||||
|
return "todo"
|
||||||
case .auth:
|
case .auth:
|
||||||
return "auth"
|
return "auth"
|
||||||
}
|
}
|
||||||
@ -490,6 +499,7 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case folderTags
|
case folderTags
|
||||||
case messageEffects
|
case messageEffects
|
||||||
case paidMessages
|
case paidMessages
|
||||||
|
case todo
|
||||||
|
|
||||||
case businessLocation
|
case businessLocation
|
||||||
case businessHours
|
case businessHours
|
||||||
@ -601,6 +611,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return "effects"
|
return "effects"
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return "paid_messages"
|
return "paid_messages"
|
||||||
|
case .todo:
|
||||||
|
return "todo"
|
||||||
case .business:
|
case .business:
|
||||||
return "business"
|
return "business"
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
@ -672,6 +684,9 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return strings.Premium_MessageEffects
|
return strings.Premium_MessageEffects
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return strings.Premium_PaidMessages
|
return strings.Premium_PaidMessages
|
||||||
|
case .todo:
|
||||||
|
//TODO:localize
|
||||||
|
return "To-Do Lists"
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return strings.Business_Location
|
return strings.Business_Location
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
@ -741,6 +756,9 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return strings.Premium_MessageEffectsInfo
|
return strings.Premium_MessageEffectsInfo
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return strings.Premium_PaidMessagesInfo
|
return strings.Premium_PaidMessagesInfo
|
||||||
|
case .todo:
|
||||||
|
//TODO:localize
|
||||||
|
return "Plan, assign and complete tasks – seamlessly and efficiently."
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return strings.Business_LocationInfo
|
return strings.Business_LocationInfo
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
@ -810,6 +828,8 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
return "Premium/Perk/MessageEffects"
|
return "Premium/Perk/MessageEffects"
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
return "Premium/Perk/PaidMessages"
|
return "Premium/Perk/PaidMessages"
|
||||||
|
case .todo:
|
||||||
|
return "Premium/Perk/PaidMessages"
|
||||||
case .businessLocation:
|
case .businessLocation:
|
||||||
return "Premium/BusinessPerk/Location"
|
return "Premium/BusinessPerk/Location"
|
||||||
case .businessHours:
|
case .businessHours:
|
||||||
|
@ -410,6 +410,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
|
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
|
||||||
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
|
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
|
||||||
dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) }
|
dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) }
|
||||||
|
dict[-1614454818] = { return Api.InputMedia.parse_inputMediaTodo($0) }
|
||||||
dict[58495792] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) }
|
dict[58495792] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) }
|
||||||
dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) }
|
dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) }
|
||||||
dict[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($0) }
|
dict[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($0) }
|
||||||
@ -558,7 +559,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
||||||
dict[-356721331] = { return Api.Message.parse_message($0) }
|
dict[-356721331] = { return Api.Message.parse_message($0) }
|
||||||
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
||||||
dict[-741178048] = { return Api.Message.parse_messageService($0) }
|
dict[2055212554] = { return Api.Message.parse_messageService($0) }
|
||||||
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
|
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
|
||||||
dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
|
dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
|
||||||
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
||||||
@ -606,6 +607,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) }
|
dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) }
|
||||||
dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
|
dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
|
||||||
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
|
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
|
||||||
|
dict[-940721021] = { return Api.MessageAction.parse_messageActionTodoAppendTasks($0) }
|
||||||
|
dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) }
|
||||||
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
|
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
|
||||||
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
|
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
|
||||||
dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) }
|
dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) }
|
||||||
@ -648,6 +651,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
|
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
|
||||||
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
||||||
dict[1758159491] = { return Api.MessageMedia.parse_messageMediaStory($0) }
|
dict[1758159491] = { return Api.MessageMedia.parse_messageMediaStory($0) }
|
||||||
|
dict[-1974226924] = { return Api.MessageMedia.parse_messageMediaToDo($0) }
|
||||||
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
||||||
dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) }
|
dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) }
|
||||||
dict[-571405253] = { return Api.MessageMedia.parse_messageMediaWebPage($0) }
|
dict[-571405253] = { return Api.MessageMedia.parse_messageMediaWebPage($0) }
|
||||||
@ -935,7 +939,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) }
|
dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) }
|
||||||
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
|
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
|
||||||
dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) }
|
dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) }
|
||||||
dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||||
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
||||||
dict[-970274264] = { return Api.StarGift.parse_starGift($0) }
|
dict[-970274264] = { return Api.StarGift.parse_starGift($0) }
|
||||||
@ -999,6 +1003,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1609668650] = { return Api.Theme.parse_theme($0) }
|
dict[-1609668650] = { return Api.Theme.parse_theme($0) }
|
||||||
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
|
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
|
||||||
dict[-7173643] = { return Api.Timezone.parse_timezone($0) }
|
dict[-7173643] = { return Api.Timezone.parse_timezone($0) }
|
||||||
|
dict[1287725239] = { return Api.TodoCompletion.parse_todoCompletion($0) }
|
||||||
|
dict[-878074577] = { return Api.TodoItem.parse_todoItem($0) }
|
||||||
|
dict[1236871718] = { return Api.TodoList.parse_todoList($0) }
|
||||||
dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) }
|
dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) }
|
||||||
dict[-39945236] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsApp($0) }
|
dict[-39945236] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsApp($0) }
|
||||||
dict[344356834] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsInline($0) }
|
dict[344356834] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsInline($0) }
|
||||||
@ -1391,7 +1398,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1404185519] = { return Api.messages.SearchResultsPositions.parse_searchResultsPositions($0) }
|
dict[1404185519] = { return Api.messages.SearchResultsPositions.parse_searchResultsPositions($0) }
|
||||||
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
|
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
|
||||||
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
|
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
|
||||||
dict[-907141753] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) }
|
dict[-2464403] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) }
|
||||||
dict[406407439] = { return Api.messages.SponsoredMessages.parse_sponsoredMessagesEmpty($0) }
|
dict[406407439] = { return Api.messages.SponsoredMessages.parse_sponsoredMessagesEmpty($0) }
|
||||||
dict[1846886166] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
dict[1846886166] = { return Api.messages.StickerSet.parse_stickerSet($0) }
|
||||||
dict[-738646805] = { return Api.messages.StickerSet.parse_stickerSetNotModified($0) }
|
dict[-738646805] = { return Api.messages.StickerSet.parse_stickerSetNotModified($0) }
|
||||||
@ -2203,6 +2210,12 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.Timezone:
|
case let _1 as Api.Timezone:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.TodoCompletion:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.TodoItem:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.TodoList:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.TopPeer:
|
case let _1 as Api.TopPeer:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.TopPeerCategory:
|
case let _1 as Api.TopPeerCategory:
|
||||||
|
@ -572,6 +572,7 @@ public extension Api {
|
|||||||
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
|
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
|
||||||
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
|
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
|
||||||
case inputMediaStory(peer: Api.InputPeer, id: Int32)
|
case inputMediaStory(peer: Api.InputPeer, id: Int32)
|
||||||
|
case inputMediaTodo(todo: Api.TodoList)
|
||||||
case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, videoCover: Api.InputPhoto?, videoTimestamp: Int32?, ttlSeconds: Int32?)
|
case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, videoCover: Api.InputPhoto?, videoTimestamp: Int32?, ttlSeconds: Int32?)
|
||||||
case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?)
|
case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?)
|
||||||
case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
||||||
@ -712,6 +713,12 @@ public extension Api {
|
|||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
|
case .inputMediaTodo(let todo):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1614454818)
|
||||||
|
}
|
||||||
|
todo.serialize(buffer, true)
|
||||||
|
break
|
||||||
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let videoTimestamp, let ttlSeconds):
|
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let videoTimestamp, let ttlSeconds):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(58495792)
|
buffer.appendInt32(58495792)
|
||||||
@ -798,6 +805,8 @@ public extension Api {
|
|||||||
return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)])
|
return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)])
|
||||||
case .inputMediaStory(let peer, let id):
|
case .inputMediaStory(let peer, let id):
|
||||||
return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)])
|
return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)])
|
||||||
|
case .inputMediaTodo(let todo):
|
||||||
|
return ("inputMediaTodo", [("todo", todo as Any)])
|
||||||
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let videoTimestamp, let ttlSeconds):
|
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let videoTimestamp, let ttlSeconds):
|
||||||
return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("videoCover", videoCover as Any), ("videoTimestamp", videoTimestamp as Any), ("ttlSeconds", ttlSeconds as Any)])
|
return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("videoCover", videoCover as Any), ("videoTimestamp", videoTimestamp as Any), ("ttlSeconds", ttlSeconds as Any)])
|
||||||
case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds):
|
case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds):
|
||||||
@ -1098,6 +1107,19 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_inputMediaTodo(_ reader: BufferReader) -> InputMedia? {
|
||||||
|
var _1: Api.TodoList?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.TodoList
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.InputMedia.inputMediaTodo(todo: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_inputMediaUploadedDocument(_ reader: BufferReader) -> InputMedia? {
|
public static func parse_inputMediaUploadedDocument(_ reader: BufferReader) -> InputMedia? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
|
@ -62,7 +62,7 @@ public extension Api {
|
|||||||
indirect enum Message: TypeConstructorDescription {
|
indirect enum Message: TypeConstructorDescription {
|
||||||
case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?)
|
case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?)
|
||||||
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
|
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
|
||||||
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?)
|
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -117,14 +117,15 @@ public extension Api {
|
|||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-741178048)
|
buffer.appendInt32(2055212554)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)}
|
||||||
peerId.serialize(buffer, true)
|
peerId.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 28) != 0 {savedPeerId!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)}
|
||||||
serializeInt32(date, buffer: buffer, boxed: false)
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
action.serialize(buffer, true)
|
action.serialize(buffer, true)
|
||||||
@ -140,8 +141,8 @@ public extension Api {
|
|||||||
return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any)])
|
return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any)])
|
||||||
case .messageEmpty(let flags, let id, let peerId):
|
case .messageEmpty(let flags, let id, let peerId):
|
||||||
return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
|
return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
|
||||||
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
||||||
return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("reactions", reactions as Any), ("ttlPeriod", ttlPeriod as Any)])
|
return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("reactions", reactions as Any), ("ttlPeriod", ttlPeriod as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,33 +300,38 @@ public extension Api {
|
|||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_4 = Api.parse(reader, signature: signature) as? Api.Peer
|
_4 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
}
|
}
|
||||||
var _5: Api.MessageReplyHeader?
|
var _5: Api.Peer?
|
||||||
|
if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_5 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
} }
|
||||||
|
var _6: Api.MessageReplyHeader?
|
||||||
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||||
_5 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader
|
_6 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader
|
||||||
} }
|
} }
|
||||||
var _6: Int32?
|
var _7: Int32?
|
||||||
_6 = reader.readInt32()
|
_7 = reader.readInt32()
|
||||||
var _7: Api.MessageAction?
|
var _8: Api.MessageAction?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_7 = Api.parse(reader, signature: signature) as? Api.MessageAction
|
_8 = Api.parse(reader, signature: signature) as? Api.MessageAction
|
||||||
}
|
}
|
||||||
var _8: Api.MessageReactions?
|
var _9: Api.MessageReactions?
|
||||||
if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() {
|
||||||
_8 = Api.parse(reader, signature: signature) as? Api.MessageReactions
|
_9 = Api.parse(reader, signature: signature) as? Api.MessageReactions
|
||||||
} }
|
} }
|
||||||
var _9: Int32?
|
var _10: Int32?
|
||||||
if Int(_1!) & Int(1 << 25) != 0 {_9 = reader.readInt32() }
|
if Int(_1!) & Int(1 << 25) != 0 {_10 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = _4 != nil
|
||||||
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 28) == 0) || _5 != nil
|
||||||
let _c6 = _6 != nil
|
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
|
||||||
let _c7 = _7 != nil
|
let _c7 = _7 != nil
|
||||||
let _c8 = (Int(_1!) & Int(1 << 20) == 0) || _8 != nil
|
let _c8 = _8 != nil
|
||||||
let _c9 = (Int(_1!) & Int(1 << 25) == 0) || _9 != nil
|
let _c9 = (Int(_1!) & Int(1 << 20) == 0) || _9 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
let _c10 = (Int(_1!) & Int(1 << 25) == 0) || _10 != nil
|
||||||
return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, reactions: _8, ttlPeriod: _9)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||||
|
return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, replyTo: _6, date: _7!, action: _8!, reactions: _9, ttlPeriod: _10)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
@ -383,6 +389,8 @@ public extension Api {
|
|||||||
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?)
|
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?)
|
||||||
case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?)
|
case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?)
|
||||||
case messageActionSuggestProfilePhoto(photo: Api.Photo)
|
case messageActionSuggestProfilePhoto(photo: Api.Photo)
|
||||||
|
case messageActionTodoAppendTasks(list: [Api.TodoItem])
|
||||||
|
case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32])
|
||||||
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
||||||
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
|
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
|
||||||
case messageActionWebViewDataSent(text: String)
|
case messageActionWebViewDataSent(text: String)
|
||||||
@ -789,6 +797,31 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
photo.serialize(buffer, true)
|
photo.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
|
case .messageActionTodoAppendTasks(let list):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-940721021)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(list.count))
|
||||||
|
for item in list {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case .messageActionTodoCompletions(let completed, let incompleted):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-864265079)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(completed.count))
|
||||||
|
for item in completed {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(incompleted.count))
|
||||||
|
for item in incompleted {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
break
|
||||||
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(228168278)
|
buffer.appendInt32(228168278)
|
||||||
@ -920,6 +953,10 @@ public extension Api {
|
|||||||
return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleStars", resaleStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any)])
|
return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleStars", resaleStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any)])
|
||||||
case .messageActionSuggestProfilePhoto(let photo):
|
case .messageActionSuggestProfilePhoto(let photo):
|
||||||
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
||||||
|
case .messageActionTodoAppendTasks(let list):
|
||||||
|
return ("messageActionTodoAppendTasks", [("list", list as Any)])
|
||||||
|
case .messageActionTodoCompletions(let completed, let incompleted):
|
||||||
|
return ("messageActionTodoCompletions", [("completed", completed as Any), ("incompleted", incompleted as Any)])
|
||||||
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
||||||
return ("messageActionTopicCreate", [("flags", flags as Any), ("title", title as Any), ("iconColor", iconColor as Any), ("iconEmojiId", iconEmojiId as Any)])
|
return ("messageActionTopicCreate", [("flags", flags as Any), ("title", title as Any), ("iconColor", iconColor as Any), ("iconEmojiId", iconEmojiId as Any)])
|
||||||
case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden):
|
case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden):
|
||||||
@ -1715,6 +1752,37 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? {
|
||||||
|
var _1: [Api.TodoItem]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.MessageAction.messageActionTodoAppendTasks(list: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_messageActionTodoCompletions(_ reader: BufferReader) -> MessageAction? {
|
||||||
|
var _1: [Int32]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||||
|
}
|
||||||
|
var _2: [Int32]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.MessageAction.messageActionTodoCompletions(completed: _1!, incompleted: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? {
|
public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
|
@ -722,6 +722,7 @@ public extension Api {
|
|||||||
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
|
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
|
||||||
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
||||||
case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?)
|
case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?)
|
||||||
|
case messageMediaToDo(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?)
|
||||||
case messageMediaUnsupported
|
case messageMediaUnsupported
|
||||||
case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
||||||
case messageMediaWebPage(flags: Int32, webpage: Api.WebPage)
|
case messageMediaWebPage(flags: Int32, webpage: Api.WebPage)
|
||||||
@ -878,6 +879,18 @@ public extension Api {
|
|||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {story!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
|
case .messageMediaToDo(let flags, let todo, let completions):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1974226924)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
todo.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(completions!.count))
|
||||||
|
for item in completions! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
break
|
||||||
case .messageMediaUnsupported:
|
case .messageMediaUnsupported:
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1618676578)
|
buffer.appendInt32(-1618676578)
|
||||||
@ -935,6 +948,8 @@ public extension Api {
|
|||||||
return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)])
|
return ("messageMediaPoll", [("poll", poll as Any), ("results", results as Any)])
|
||||||
case .messageMediaStory(let flags, let peer, let id, let story):
|
case .messageMediaStory(let flags, let peer, let id, let story):
|
||||||
return ("messageMediaStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)])
|
return ("messageMediaStory", [("flags", flags as Any), ("peer", peer as Any), ("id", id as Any), ("story", story as Any)])
|
||||||
|
case .messageMediaToDo(let flags, let todo, let completions):
|
||||||
|
return ("messageMediaToDo", [("flags", flags as Any), ("todo", todo as Any), ("completions", completions as Any)])
|
||||||
case .messageMediaUnsupported:
|
case .messageMediaUnsupported:
|
||||||
return ("messageMediaUnsupported", [])
|
return ("messageMediaUnsupported", [])
|
||||||
case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType):
|
case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType):
|
||||||
@ -1262,6 +1277,27 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_messageMediaToDo(_ reader: BufferReader) -> MessageMedia? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.TodoList?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.TodoList
|
||||||
|
}
|
||||||
|
var _3: [Api.TodoCompletion]?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoCompletion.self)
|
||||||
|
} }
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.MessageMedia.messageMediaToDo(flags: _1!, todo: _2!, completions: _3)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? {
|
public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? {
|
||||||
return Api.MessageMedia.messageMediaUnsupported
|
return Api.MessageMedia.messageMediaUnsupported
|
||||||
}
|
}
|
||||||
|
@ -442,13 +442,13 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
indirect enum SponsoredMessage: TypeConstructorDescription {
|
indirect enum SponsoredMessage: TypeConstructorDescription {
|
||||||
case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, media: Api.MessageMedia?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?)
|
case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, media: Api.MessageMedia?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?, minDisplayDuration: Int32?, maxDisplayDuration: Int32?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo):
|
case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo, let minDisplayDuration, let maxDisplayDuration):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1301522832)
|
buffer.appendInt32(2109703795)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeBytes(randomId, buffer: buffer, boxed: false)
|
serializeBytes(randomId, buffer: buffer, boxed: false)
|
||||||
@ -466,14 +466,16 @@ public extension Api {
|
|||||||
serializeString(buttonText, buffer: buffer, boxed: false)
|
serializeString(buttonText, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 8) != 0 {serializeString(additionalInfo!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 8) != 0 {serializeString(additionalInfo!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(minDisplayDuration!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(maxDisplayDuration!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo):
|
case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo, let minDisplayDuration, let maxDisplayDuration):
|
||||||
return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("media", media as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)])
|
return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("media", media as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any), ("minDisplayDuration", minDisplayDuration as Any), ("maxDisplayDuration", maxDisplayDuration as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,6 +512,10 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 7) != 0 {_11 = parseString(reader) }
|
if Int(_1!) & Int(1 << 7) != 0 {_11 = parseString(reader) }
|
||||||
var _12: String?
|
var _12: String?
|
||||||
if Int(_1!) & Int(1 << 8) != 0 {_12 = parseString(reader) }
|
if Int(_1!) & Int(1 << 8) != 0 {_12 = parseString(reader) }
|
||||||
|
var _13: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 15) != 0 {_13 = reader.readInt32() }
|
||||||
|
var _14: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 15) != 0 {_14 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
@ -522,8 +528,10 @@ public extension Api {
|
|||||||
let _c10 = _10 != nil
|
let _c10 = _10 != nil
|
||||||
let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
|
let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
|
||||||
let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
|
let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
|
let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil
|
||||||
return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12)
|
let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
|
||||||
|
return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12, minDisplayDuration: _13, maxDisplayDuration: _14)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -762,6 +762,144 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum TodoCompletion: TypeConstructorDescription {
|
||||||
|
case todoCompletion(id: Int32, completedBy: Int64, date: Int32)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .todoCompletion(let id, let completedBy, let date):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(1287725239)
|
||||||
|
}
|
||||||
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
|
serializeInt64(completedBy, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .todoCompletion(let id, let completedBy, let date):
|
||||||
|
return ("todoCompletion", [("id", id as Any), ("completedBy", completedBy as Any), ("date", date as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_todoCompletion(_ reader: BufferReader) -> TodoCompletion? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Int64?
|
||||||
|
_2 = reader.readInt64()
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.TodoCompletion.todoCompletion(id: _1!, completedBy: _2!, date: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum TodoItem: TypeConstructorDescription {
|
||||||
|
case todoItem(id: Int32, title: Api.TextWithEntities)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .todoItem(let id, let title):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-878074577)
|
||||||
|
}
|
||||||
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
|
title.serialize(buffer, true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .todoItem(let id, let title):
|
||||||
|
return ("todoItem", [("id", id as Any), ("title", title as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_todoItem(_ reader: BufferReader) -> TodoItem? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.TextWithEntities?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.TodoItem.todoItem(id: _1!, title: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum TodoList: TypeConstructorDescription {
|
||||||
|
case todoList(flags: Int32, title: Api.TextWithEntities, list: [Api.TodoItem])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .todoList(let flags, let title, let list):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(1236871718)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
title.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(list.count))
|
||||||
|
for item in list {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .todoList(let flags, let title, let list):
|
||||||
|
return ("todoList", [("flags", flags as Any), ("title", title as Any), ("list", list as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_todoList(_ reader: BufferReader) -> TodoList? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.TextWithEntities?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||||
|
}
|
||||||
|
var _3: [Api.TodoItem]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.TodoList.todoList(flags: _1!, title: _2!, list: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum TopPeer: TypeConstructorDescription {
|
enum TopPeer: TypeConstructorDescription {
|
||||||
case topPeer(peer: Api.Peer, rating: Double)
|
case topPeer(peer: Api.Peer, rating: Double)
|
||||||
|
@ -304,17 +304,19 @@ public extension Api.messages {
|
|||||||
}
|
}
|
||||||
public extension Api.messages {
|
public extension Api.messages {
|
||||||
enum SponsoredMessages: TypeConstructorDescription {
|
enum SponsoredMessages: TypeConstructorDescription {
|
||||||
case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User])
|
case sponsoredMessages(flags: Int32, postsBetween: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User])
|
||||||
case sponsoredMessagesEmpty
|
case sponsoredMessagesEmpty
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users):
|
case .sponsoredMessages(let flags, let postsBetween, let startDelay, let betweenDelay, let messages, let chats, let users):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-907141753)
|
buffer.appendInt32(-2464403)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(startDelay!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(betweenDelay!, buffer: buffer, boxed: false)}
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(messages.count))
|
buffer.appendInt32(Int32(messages.count))
|
||||||
for item in messages {
|
for item in messages {
|
||||||
@ -342,8 +344,8 @@ public extension Api.messages {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users):
|
case .sponsoredMessages(let flags, let postsBetween, let startDelay, let betweenDelay, let messages, let chats, let users):
|
||||||
return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
|
return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("startDelay", startDelay as Any), ("betweenDelay", betweenDelay as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
case .sponsoredMessagesEmpty:
|
case .sponsoredMessagesEmpty:
|
||||||
return ("sponsoredMessagesEmpty", [])
|
return ("sponsoredMessagesEmpty", [])
|
||||||
}
|
}
|
||||||
@ -354,25 +356,31 @@ public extension Api.messages {
|
|||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: Int32?
|
var _2: Int32?
|
||||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
|
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
|
||||||
var _3: [Api.SponsoredMessage]?
|
var _3: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
|
||||||
|
var _4: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() }
|
||||||
|
var _5: [Api.SponsoredMessage]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self)
|
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self)
|
||||||
}
|
}
|
||||||
var _4: [Api.Chat]?
|
var _6: [Api.Chat]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
}
|
}
|
||||||
var _5: [Api.User]?
|
var _7: [Api.User]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
}
|
}
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||||
let _c5 = _5 != nil
|
let _c5 = _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = _6 != nil
|
||||||
return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, messages: _3!, chats: _4!, users: _5!)
|
let _c7 = _7 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||||
|
return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, startDelay: _3, betweenDelay: _4, messages: _5!, chats: _6!, users: _7!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -21,22 +21,6 @@ public extension Api.functions.account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.account {
|
|
||||||
static func addNoPaidMessagesException(flags: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(1869122215)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
userId.serialize(buffer, true)
|
|
||||||
return (FunctionDescription(name: "account.addNoPaidMessagesException", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Bool?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -666,11 +650,13 @@ public extension Api.functions.account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func getPaidMessagesRevenue(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.PaidMessagesRevenue>) {
|
static func getPaidMessagesRevenue(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.PaidMessagesRevenue>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-249139400)
|
buffer.appendInt32(431639143)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {parentPeer!.serialize(buffer, true)}
|
||||||
userId.serialize(buffer, true)
|
userId.serialize(buffer, true)
|
||||||
return (FunctionDescription(name: "account.getPaidMessagesRevenue", parameters: [("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PaidMessagesRevenue? in
|
return (FunctionDescription(name: "account.getPaidMessagesRevenue", parameters: [("flags", String(describing: flags)), ("parentPeer", String(describing: parentPeer)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PaidMessagesRevenue? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.account.PaidMessagesRevenue?
|
var result: Api.account.PaidMessagesRevenue?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
@ -1437,6 +1423,23 @@ public extension Api.functions.account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.account {
|
||||||
|
static func toggleNoPaidMessagesException(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-30483850)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {parentPeer!.serialize(buffer, true)}
|
||||||
|
userId.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "account.toggleNoPaidMessagesException", parameters: [("flags", String(describing: flags)), ("parentPeer", String(describing: parentPeer)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -5067,6 +5070,27 @@ public extension Api.functions.messages {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.messages {
|
||||||
|
static func appendTodoList(peer: Api.InputPeer, msgId: Int32, list: [Api.TodoItem]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(564531287)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(list.count))
|
||||||
|
for item in list {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.appendTodoList", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("list", String(describing: list))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.messages {
|
public extension Api.functions.messages {
|
||||||
static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.ChatInvite>) {
|
static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.ChatInvite>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -7065,11 +7089,13 @@ public extension Api.functions.messages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.messages {
|
public extension Api.functions.messages {
|
||||||
static func getSponsoredMessages(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SponsoredMessages>) {
|
static func getSponsoredMessages(flags: Int32, peer: Api.InputPeer, msgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SponsoredMessages>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-1680673735)
|
buffer.appendInt32(1030547536)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
return (FunctionDescription(name: "messages.getSponsoredMessages", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.getSponsoredMessages", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.messages.SponsoredMessages?
|
var result: Api.messages.SponsoredMessages?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
@ -8880,6 +8906,32 @@ public extension Api.functions.messages {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.messages {
|
||||||
|
static func toggleTodoCompleted(peer: Api.InputPeer, msgId: Int32, completed: [Int32], incompleted: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-740282076)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(completed.count))
|
||||||
|
for item in completed {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(incompleted.count))
|
||||||
|
for item in incompleted {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.toggleTodoCompleted", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("completed", String(describing: completed)), ("incompleted", String(describing: incompleted))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.messages {
|
public extension Api.functions.messages {
|
||||||
static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.TranscribedAudio>) {
|
static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.TranscribedAudio>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -233,6 +233,9 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) })
|
declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) })
|
||||||
declareEncodable(ReportDeliveryMessageAttribute.self, f: { ReportDeliveryMessageAttribute(decoder: $0) })
|
declareEncodable(ReportDeliveryMessageAttribute.self, f: { ReportDeliveryMessageAttribute(decoder: $0) })
|
||||||
declareEncodable(PaidStarsMessageAttribute.self, f: { PaidStarsMessageAttribute(decoder: $0) })
|
declareEncodable(PaidStarsMessageAttribute.self, f: { PaidStarsMessageAttribute(decoder: $0) })
|
||||||
|
declareEncodable(TelegramMediaTodo.self, f: { TelegramMediaTodo(decoder: $0) })
|
||||||
|
declareEncodable(TelegramMediaTodo.Item.self, f: { TelegramMediaTodo.Item(decoder: $0) })
|
||||||
|
declareEncodable(TelegramMediaTodo.Completion.self, f: { TelegramMediaTodo.Completion(decoder: $0) })
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -15,8 +15,10 @@ public final class AdMessageAttribute: MessageAttribute {
|
|||||||
public let additionalInfo: String?
|
public let additionalInfo: String?
|
||||||
public let canReport: Bool
|
public let canReport: Bool
|
||||||
public let hasContentMedia: Bool
|
public let hasContentMedia: Bool
|
||||||
|
public let minDisplayDuration: Int32?
|
||||||
|
public let maxDisplayDuration: Int32?
|
||||||
|
|
||||||
public init(opaqueId: Data, messageType: MessageType, url: String, buttonText: String, sponsorInfo: String?, additionalInfo: String?, canReport: Bool, hasContentMedia: Bool) {
|
public init(opaqueId: Data, messageType: MessageType, url: String, buttonText: String, sponsorInfo: String?, additionalInfo: String?, canReport: Bool, hasContentMedia: Bool, minDisplayDuration: Int32?, maxDisplayDuration: Int32?) {
|
||||||
self.opaqueId = opaqueId
|
self.opaqueId = opaqueId
|
||||||
self.messageType = messageType
|
self.messageType = messageType
|
||||||
self.url = url
|
self.url = url
|
||||||
@ -25,6 +27,8 @@ public final class AdMessageAttribute: MessageAttribute {
|
|||||||
self.additionalInfo = additionalInfo
|
self.additionalInfo = additionalInfo
|
||||||
self.canReport = canReport
|
self.canReport = canReport
|
||||||
self.hasContentMedia = hasContentMedia
|
self.hasContentMedia = hasContentMedia
|
||||||
|
self.minDisplayDuration = minDisplayDuration
|
||||||
|
self.maxDisplayDuration = maxDisplayDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
|
@ -137,7 +137,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _, _):
|
||||||
return chatPeerId.peerId
|
return chatPeerId.peerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
|||||||
return result
|
return result
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return []
|
return []
|
||||||
case let .messageService(_, _, fromId, chatPeerId, _, _, action, _, _):
|
case let .messageService(_, _, fromId, chatPeerId, savedPeerId, _, _, action, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
var result = [peerId]
|
var result = [peerId]
|
||||||
|
|
||||||
@ -227,9 +227,12 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
|||||||
if resolvedFromId != peerId {
|
if resolvedFromId != peerId {
|
||||||
result.append(resolvedFromId)
|
result.append(resolvedFromId)
|
||||||
}
|
}
|
||||||
|
if let savedPeerId, resolvedFromId != savedPeerId.peerId {
|
||||||
|
result.append(savedPeerId.peerId)
|
||||||
|
}
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice:
|
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks:
|
||||||
break
|
break
|
||||||
case let .messageActionChannelMigrateFrom(_, chatId):
|
case let .messageActionChannelMigrateFrom(_, chatId):
|
||||||
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))
|
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))
|
||||||
@ -301,7 +304,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
|||||||
}
|
}
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
break
|
break
|
||||||
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _, _):
|
case let .messageService(_, id, _, chatPeerId, _, replyHeader, _, _, _, _):
|
||||||
if let replyHeader = replyHeader {
|
if let replyHeader = replyHeader {
|
||||||
switch replyHeader {
|
switch replyHeader {
|
||||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
||||||
@ -428,6 +431,30 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
|
|
||||||
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil, nil)
|
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
case let .messageMediaToDo(_, todo, completions):
|
||||||
|
switch todo {
|
||||||
|
case let .todoList(apiFlags, title, list):
|
||||||
|
var flags: TelegramMediaTodo.Flags = []
|
||||||
|
if (apiFlags & (1 << 0)) != 0 {
|
||||||
|
flags.insert(.othersCanAppend)
|
||||||
|
}
|
||||||
|
if (apiFlags & (1 << 1)) != 0 {
|
||||||
|
flags.insert(.othersCanComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
let todoText: String
|
||||||
|
let todoEntities: [MessageTextEntity]
|
||||||
|
switch title {
|
||||||
|
case let .textWithEntities(text, entities):
|
||||||
|
todoText = text
|
||||||
|
todoEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
|
}
|
||||||
|
var todoCompletions: [TelegramMediaTodo.Completion] = []
|
||||||
|
if let completions {
|
||||||
|
todoCompletions = completions.map(TelegramMediaTodo.Completion.init(apiCompletion:))
|
||||||
|
}
|
||||||
|
return (TelegramMediaTodo(flags: flags, text: todoText, textEntities: todoEntities, items: list.map(TelegramMediaTodo.Item.init(apiItem:)), completions: todoCompletions), nil, nil, nil, nil, nil)
|
||||||
|
}
|
||||||
case let .messageMediaDice(value, emoticon):
|
case let .messageMediaDice(value, emoticon):
|
||||||
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil, nil)
|
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil, nil)
|
||||||
case let .messageMediaStory(flags, peerId, id, _):
|
case let .messageMediaStory(flags, peerId, id, _):
|
||||||
@ -1037,14 +1064,16 @@ extension StoreMessage {
|
|||||||
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
|
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return nil
|
return nil
|
||||||
case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action, reactions, ttlPeriod):
|
case let .messageService(flags, id, fromId, chatPeerId, savedPeerId, replyTo, date, action, reactions, ttlPeriod):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
let authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
let authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
||||||
|
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
|
|
||||||
var threadId: Int64?
|
var threadId: Int64?
|
||||||
if let replyTo = replyTo {
|
if let savedPeerId {
|
||||||
|
threadId = savedPeerId.peerId.toInt64()
|
||||||
|
} else if let replyTo = replyTo {
|
||||||
var threadMessageId: MessageId?
|
var threadMessageId: MessageId?
|
||||||
switch replyTo {
|
switch replyTo {
|
||||||
case let .messageReplyHeader(innerFlags, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
case let .messageReplyHeader(innerFlags, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
||||||
|
@ -224,6 +224,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||||||
flags: mappedFlags,
|
flags: mappedFlags,
|
||||||
otherParticipants: otherParticipants.flatMap({ return $0.map(\.peerId) }) ?? []
|
otherParticipants: otherParticipants.flatMap({ return $0.map(\.peerId) }) ?? []
|
||||||
)))
|
)))
|
||||||
|
case let .messageActionTodoCompletions(completed, incompleted):
|
||||||
|
return TelegramMediaAction(action: .todoCompletions(completed: completed, incompleted: incompleted))
|
||||||
|
case let .messageActionTodoAppendTasks(list):
|
||||||
|
return TelegramMediaAction(action: .todoAppendTasks(list.map { TelegramMediaTodo.Item(apiItem: $0) }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import Foundation
|
||||||
|
import Postbox
|
||||||
|
import TelegramApi
|
||||||
|
|
||||||
|
|
||||||
|
extension TelegramMediaTodo.Item {
|
||||||
|
init(apiItem: Api.TodoItem) {
|
||||||
|
switch apiItem {
|
||||||
|
case let .todoItem(id, title):
|
||||||
|
let itemText: String
|
||||||
|
let itemEntities: [MessageTextEntity]
|
||||||
|
switch title {
|
||||||
|
case let .textWithEntities(text, entities):
|
||||||
|
itemText = text
|
||||||
|
itemEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
|
}
|
||||||
|
self.init(text: itemText, entities: itemEntities, id: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiItem: Api.TodoItem {
|
||||||
|
return .todoItem(id: self.id, title: .textWithEntities(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TelegramMediaTodo.Completion {
|
||||||
|
init(apiCompletion: Api.TodoCompletion) {
|
||||||
|
switch apiCompletion {
|
||||||
|
case let .todoCompletion(id, completedBy, date):
|
||||||
|
self.init(id: id, date: date, completedBy: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(completedBy)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -319,6 +319,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|
|||||||
if poll.deadlineTimeout != nil {
|
if poll.deadlineTimeout != nil {
|
||||||
pollFlags |= 1 << 4
|
pollFlags |= 1 << 4
|
||||||
}
|
}
|
||||||
|
|
||||||
var mappedSolution: String?
|
var mappedSolution: String?
|
||||||
var mappedSolutionEntities: [Api.MessageEntity]?
|
var mappedSolutionEntities: [Api.MessageEntity]?
|
||||||
if let solution = poll.results.solution {
|
if let solution = poll.results.solution {
|
||||||
@ -328,10 +329,20 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|
|||||||
}
|
}
|
||||||
let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)
|
let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: .textWithEntities(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary())), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)
|
||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
} else if let media = media as? TelegramMediaDice {
|
} else if let todo = media as? TelegramMediaTodo {
|
||||||
let inputDice = Api.InputMedia.inputMediaDice(emoticon: media.emoji)
|
var flags: Int32 = 0
|
||||||
|
if todo.flags.contains(.othersCanAppend) {
|
||||||
|
flags |= 1 << 0
|
||||||
|
}
|
||||||
|
if todo.flags.contains(.othersCanComplete) {
|
||||||
|
flags |= 1 << 1
|
||||||
|
}
|
||||||
|
let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem }))
|
||||||
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
|
} else if let dice = media as? TelegramMediaDice {
|
||||||
|
let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
|
||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
} else if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content {
|
} else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content {
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
flags |= 1 << 2
|
flags |= 1 << 2
|
||||||
if let attribute = attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute {
|
if let attribute = attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute {
|
||||||
|
@ -68,7 +68,18 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
|||||||
}
|
}
|
||||||
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil)
|
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil)
|
||||||
}
|
}
|
||||||
if let uploadSignal = generateUploadSignal(forceReupload) {
|
if let todo = media.media as? TelegramMediaTodo {
|
||||||
|
var flags: Int32 = 0
|
||||||
|
if todo.flags.contains(.othersCanAppend) {
|
||||||
|
flags |= 1 << 0
|
||||||
|
}
|
||||||
|
if todo.flags.contains(.othersCanComplete) {
|
||||||
|
flags |= 1 << 1
|
||||||
|
}
|
||||||
|
let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem }))
|
||||||
|
uploadedMedia = .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||||
|
}
|
||||||
|
else if let uploadSignal = generateUploadSignal(forceReupload) {
|
||||||
uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027)))
|
uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027)))
|
||||||
|> then(uploadSignal)
|
|> then(uploadSignal)
|
||||||
|> map { result -> PendingMessageUploadedContentResult? in
|
|> map { result -> PendingMessageUploadedContentResult? in
|
||||||
@ -110,7 +121,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
|||||||
if text.isEmpty {
|
if text.isEmpty {
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
switch media {
|
switch media {
|
||||||
case _ as TelegramMediaImage, _ as TelegramMediaFile:
|
case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaTodo:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if let _ = scheduleTime {
|
if let _ = scheduleTime {
|
||||||
|
@ -108,7 +108,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||||||
updatedTimestamp = date
|
updatedTimestamp = date
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
break
|
break
|
||||||
case let .messageService(_, _, _, _, _, date, _, _, _):
|
case let .messageService(_, _, _, _, _, _, date, _, _, _):
|
||||||
updatedTimestamp = date
|
updatedTimestamp = date
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,7 +11,7 @@ func _internal_getPaidMessagesRevenue(account: Account, peerId: PeerId) -> Signa
|
|||||||
guard let inputUser else {
|
guard let inputUser else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.account.getPaidMessagesRevenue(userId: inputUser))
|
return account.network.request(Api.functions.account.getPaidMessagesRevenue(flags: 0, parentPeer: nil, userId: inputUser))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.account.PaidMessagesRevenue?, NoError> in
|
|> `catch` { _ -> Signal<Api.account.PaidMessagesRevenue?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -40,7 +40,7 @@ func _internal_addNoPaidMessagesException(account: Account, peerId: PeerId, refu
|
|||||||
if refundCharged {
|
if refundCharged {
|
||||||
flags |= (1 << 0)
|
flags |= (1 << 0)
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.account.addNoPaidMessagesException(flags: flags, userId: inputUser))
|
return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: nil, userId: inputUser))
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
return .single(.boolFalse)
|
return .single(.boolFalse)
|
||||||
} |> mapToSignal { _ in
|
} |> mapToSignal { _ in
|
||||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 204
|
return 205
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
@ -108,7 +108,7 @@ extension Api.Message {
|
|||||||
return id
|
return id
|
||||||
case let .messageEmpty(_, id, _):
|
case let .messageEmpty(_, id, _):
|
||||||
return id
|
return id
|
||||||
case let .messageService(_, id, _, _, _, _, _, _, _):
|
case let .messageService(_, id, _, _, _, _, _, _, _, _):
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ extension Api.Message {
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case let .messageService(_, id, _, chatPeerId, _, _, _, _, _):
|
case let .messageService(_, id, _, chatPeerId, _, _, _, _, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ extension Api.Message {
|
|||||||
return peerId
|
return peerId
|
||||||
case let .messageEmpty(_, _, peerId):
|
case let .messageEmpty(_, _, peerId):
|
||||||
return peerId?.peerId
|
return peerId?.peerId
|
||||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
return peerId
|
return peerId
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ extension Api.Message {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return date
|
return date
|
||||||
case let .messageService(_, _, _, _, _, date, _, _, _):
|
case let .messageService(_, _, _, _, _, _, date, _, _, _):
|
||||||
return date
|
return date
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return nil
|
return nil
|
||||||
|
@ -160,6 +160,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||||
case conferenceCall(ConferenceCall)
|
case conferenceCall(ConferenceCall)
|
||||||
|
case todoCompletions(completed: [Int32], incompleted: [Int32])
|
||||||
|
case todoAppendTasks([TelegramMediaTodo.Item])
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
||||||
@ -295,6 +297,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
flags: ConferenceCall.Flags(rawValue: decoder.decodeInt32ForKey("flags", orElse: 0)),
|
flags: ConferenceCall.Flags(rawValue: decoder.decodeInt32ForKey("flags", orElse: 0)),
|
||||||
otherParticipants: decoder.decodeInt64ArrayForKey("part").map(PeerId.init)
|
otherParticipants: decoder.decodeInt64ArrayForKey("part").map(PeerId.init)
|
||||||
))
|
))
|
||||||
|
case 49:
|
||||||
|
self = .todoCompletions(
|
||||||
|
completed: decoder.decodeInt32ArrayForKey("completed"),
|
||||||
|
incompleted: decoder.decodeInt32ArrayForKey("incompleted")
|
||||||
|
)
|
||||||
|
case 50:
|
||||||
|
self = .todoAppendTasks(
|
||||||
|
decoder.decodeObjectArrayWithDecoderForKey("tasks")
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
self = .unknown
|
self = .unknown
|
||||||
}
|
}
|
||||||
@ -698,6 +709,13 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
}
|
}
|
||||||
encoder.encodeInt32(conferenceCall.flags.rawValue, forKey: "flags")
|
encoder.encodeInt32(conferenceCall.flags.rawValue, forKey: "flags")
|
||||||
encoder.encodeInt64Array(conferenceCall.otherParticipants.map({ $0.toInt64() }), forKey: "part")
|
encoder.encodeInt64Array(conferenceCall.otherParticipants.map({ $0.toInt64() }), forKey: "part")
|
||||||
|
case let .todoCompletions(completed, incompleted):
|
||||||
|
encoder.encodeInt32(49, forKey: "_rawValue")
|
||||||
|
encoder.encodeInt32Array(completed, forKey: "completed")
|
||||||
|
encoder.encodeInt32Array(incompleted, forKey: "incompleted")
|
||||||
|
case let .todoAppendTasks(tasks):
|
||||||
|
encoder.encodeInt32(50, forKey: "_rawValue")
|
||||||
|
encoder.encodeObjectArray(tasks, forKey: "tasks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
import Foundation
|
||||||
|
import Postbox
|
||||||
|
|
||||||
|
public final class TelegramMediaTodo: Media, Equatable {
|
||||||
|
public struct Flags: OptionSet {
|
||||||
|
public var rawValue: Int32
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
self.rawValue = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(rawValue: Int32) {
|
||||||
|
self.rawValue = rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public static let othersCanAppend = Flags(rawValue: 1 << 0)
|
||||||
|
public static let othersCanComplete = Flags(rawValue: 1 << 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Item: Equatable, PostboxCoding {
|
||||||
|
public let text: String
|
||||||
|
public let entities: [MessageTextEntity]
|
||||||
|
public let id: Int32
|
||||||
|
|
||||||
|
public init(text: String, entities: [MessageTextEntity], id: Int32) {
|
||||||
|
self.text = text
|
||||||
|
self.entities = entities
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.text = decoder.decodeStringForKey("t", orElse: "")
|
||||||
|
self.entities = decoder.decodeObjectArrayWithDecoderForKey("et")
|
||||||
|
self.id = decoder.decodeInt32ForKey("i", orElse: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeString(self.text, forKey: "t")
|
||||||
|
encoder.encodeObjectArray(self.entities, forKey: "et")
|
||||||
|
encoder.encodeInt32(self.id, forKey: "i")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Completion: Equatable, PostboxCoding {
|
||||||
|
public let id: Int32
|
||||||
|
public let date: Int32
|
||||||
|
public let completedBy: EnginePeer.Id
|
||||||
|
|
||||||
|
public init(id: Int32, date: Int32, completedBy: EnginePeer.Id) {
|
||||||
|
self.id = id
|
||||||
|
self.date = date
|
||||||
|
self.completedBy = completedBy
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.id = decoder.decodeInt32ForKey("i", orElse: 0)
|
||||||
|
self.date = decoder.decodeInt32ForKey("d", orElse: 0)
|
||||||
|
self.completedBy = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeInt32(self.id, forKey: "i")
|
||||||
|
encoder.encodeInt32(self.date, forKey: "d")
|
||||||
|
encoder.encodeInt64(self.completedBy.toInt64(), forKey: "p")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var id: MediaId? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
public var peerIds: [PeerId] {
|
||||||
|
return self.completions.map { $0.completedBy }
|
||||||
|
}
|
||||||
|
|
||||||
|
public let flags: Flags
|
||||||
|
public let text: String
|
||||||
|
public let textEntities: [MessageTextEntity]
|
||||||
|
public let items: [Item]
|
||||||
|
public let completions: [Completion]
|
||||||
|
|
||||||
|
public init(flags: Flags, text: String, textEntities: [MessageTextEntity], items: [Item], completions: [Completion] = []) {
|
||||||
|
self.flags = flags
|
||||||
|
self.text = text
|
||||||
|
self.textEntities = textEntities
|
||||||
|
self.items = items
|
||||||
|
self.completions = completions
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.flags = Flags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
|
||||||
|
self.text = decoder.decodeStringForKey("t", orElse: "")
|
||||||
|
self.textEntities = decoder.decodeObjectArrayWithDecoderForKey("te")
|
||||||
|
self.items = decoder.decodeObjectArrayWithDecoderForKey("is")
|
||||||
|
self.completions = decoder.decodeObjectArrayWithDecoderForKey("cs")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
|
||||||
|
encoder.encodeString(self.text, forKey: "t")
|
||||||
|
encoder.encodeObjectArray(self.textEntities, forKey: "te")
|
||||||
|
encoder.encodeObjectArray(self.items, forKey: "is")
|
||||||
|
encoder.encodeObjectArray(self.completions, forKey: "cs")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isEqual(to other: Media) -> Bool {
|
||||||
|
guard let other = other as? TelegramMediaTodo else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return self == other
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isSemanticallyEqual(to other: Media) -> Bool {
|
||||||
|
return self.isEqual(to: other)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TelegramMediaTodo, rhs: TelegramMediaTodo) -> Bool {
|
||||||
|
if lhs.flags != rhs.flags {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textEntities != rhs.textEntities {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.completions != rhs.completions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdated(items: [TelegramMediaTodo.Item]) -> TelegramMediaTodo {
|
||||||
|
return TelegramMediaTodo(
|
||||||
|
flags: self.flags,
|
||||||
|
text: self.text,
|
||||||
|
textEntities: self.textEntities,
|
||||||
|
items: items,
|
||||||
|
completions: self.completions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdated(completions: [TelegramMediaTodo.Completion]) -> TelegramMediaTodo {
|
||||||
|
return TelegramMediaTodo(
|
||||||
|
flags: self.flags,
|
||||||
|
text: self.text,
|
||||||
|
textEntities: self.textEntities,
|
||||||
|
items: self.items,
|
||||||
|
completions: completions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
case sponsorInfo
|
case sponsorInfo
|
||||||
case additionalInfo
|
case additionalInfo
|
||||||
case canReport
|
case canReport
|
||||||
|
case minDisplayDuration
|
||||||
|
case maxDisplayDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageType: Int32, Codable {
|
enum MessageType: Int32, Codable {
|
||||||
@ -41,6 +43,8 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
public let sponsorInfo: String?
|
public let sponsorInfo: String?
|
||||||
public let additionalInfo: String?
|
public let additionalInfo: String?
|
||||||
public let canReport: Bool
|
public let canReport: Bool
|
||||||
|
public let minDisplayDuration: Int32?
|
||||||
|
public let maxDisplayDuration: Int32?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
opaqueId: Data,
|
opaqueId: Data,
|
||||||
@ -56,7 +60,9 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
buttonText: String,
|
buttonText: String,
|
||||||
sponsorInfo: String?,
|
sponsorInfo: String?,
|
||||||
additionalInfo: String?,
|
additionalInfo: String?,
|
||||||
canReport: Bool
|
canReport: Bool,
|
||||||
|
minDisplayDuration: Int32?,
|
||||||
|
maxDisplayDuration: Int32?
|
||||||
) {
|
) {
|
||||||
self.opaqueId = opaqueId
|
self.opaqueId = opaqueId
|
||||||
self.messageType = messageType
|
self.messageType = messageType
|
||||||
@ -72,6 +78,8 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
self.sponsorInfo = sponsorInfo
|
self.sponsorInfo = sponsorInfo
|
||||||
self.additionalInfo = additionalInfo
|
self.additionalInfo = additionalInfo
|
||||||
self.canReport = canReport
|
self.canReport = canReport
|
||||||
|
self.minDisplayDuration = minDisplayDuration
|
||||||
|
self.maxDisplayDuration = maxDisplayDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -109,6 +117,9 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
self.additionalInfo = try container.decodeIfPresent(String.self, forKey: .additionalInfo)
|
self.additionalInfo = try container.decodeIfPresent(String.self, forKey: .additionalInfo)
|
||||||
|
|
||||||
self.canReport = try container.decodeIfPresent(Bool.self, forKey: .canReport) ?? false
|
self.canReport = try container.decodeIfPresent(Bool.self, forKey: .canReport) ?? false
|
||||||
|
|
||||||
|
self.minDisplayDuration = try container.decodeIfPresent(Int32.self, forKey: .minDisplayDuration)
|
||||||
|
self.maxDisplayDuration = try container.decodeIfPresent(Int32.self, forKey: .maxDisplayDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -144,6 +155,9 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
try container.encodeIfPresent(self.additionalInfo, forKey: .additionalInfo)
|
try container.encodeIfPresent(self.additionalInfo, forKey: .additionalInfo)
|
||||||
|
|
||||||
try container.encode(self.canReport, forKey: .canReport)
|
try container.encode(self.canReport, forKey: .canReport)
|
||||||
|
|
||||||
|
try container.encodeIfPresent(self.minDisplayDuration, forKey: .minDisplayDuration)
|
||||||
|
try container.encodeIfPresent(self.maxDisplayDuration, forKey: .maxDisplayDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: CachedMessage, rhs: CachedMessage) -> Bool {
|
public static func ==(lhs: CachedMessage, rhs: CachedMessage) -> Bool {
|
||||||
@ -193,6 +207,12 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
if lhs.canReport != rhs.canReport {
|
if lhs.canReport != rhs.canReport {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.minDisplayDuration != rhs.minDisplayDuration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.maxDisplayDuration != rhs.maxDisplayDuration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,10 +226,22 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
case .recommended:
|
case .recommended:
|
||||||
mappedMessageType = .recommended
|
mappedMessageType = .recommended
|
||||||
}
|
}
|
||||||
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, url: self.url, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo, canReport: self.canReport, hasContentMedia: !self.contentMedia.isEmpty))
|
let adAttribute = AdMessageAttribute(
|
||||||
|
opaqueId: self.opaqueId,
|
||||||
|
messageType: mappedMessageType,
|
||||||
|
url: self.url,
|
||||||
|
buttonText: self.buttonText,
|
||||||
|
sponsorInfo: self.sponsorInfo,
|
||||||
|
additionalInfo: self.additionalInfo,
|
||||||
|
canReport: self.canReport,
|
||||||
|
hasContentMedia: !self.contentMedia.isEmpty,
|
||||||
|
minDisplayDuration: self.minDisplayDuration,
|
||||||
|
maxDisplayDuration: self.maxDisplayDuration
|
||||||
|
)
|
||||||
|
attributes.append(adAttribute)
|
||||||
if !self.textEntities.isEmpty {
|
if !self.textEntities.isEmpty {
|
||||||
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
|
let entitiesAttribute = TextEntitiesMessageAttribute(entities: self.textEntities)
|
||||||
attributes.append(attribute)
|
attributes.append(entitiesAttribute)
|
||||||
}
|
}
|
||||||
|
|
||||||
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
||||||
@ -282,7 +314,8 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let peerId: PeerId
|
private let peerId: EnginePeer.Id
|
||||||
|
private let messageId: EngineMessage.Id?
|
||||||
|
|
||||||
private let maskAsSeenDisposables = DisposableDict<Data>()
|
private let maskAsSeenDisposables = DisposableDict<Data>()
|
||||||
|
|
||||||
@ -369,12 +402,20 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
struct State: Equatable {
|
struct State: Equatable {
|
||||||
var interPostInterval: Int32?
|
var interPostInterval: Int32?
|
||||||
|
var startDelay: Int32?
|
||||||
|
var betweenDelay: Int32?
|
||||||
var messages: [Message]
|
var messages: [Message]
|
||||||
|
|
||||||
static func ==(lhs: State, rhs: State) -> Bool {
|
static func ==(lhs: State, rhs: State) -> Bool {
|
||||||
if lhs.interPostInterval != rhs.interPostInterval {
|
if lhs.interPostInterval != rhs.interPostInterval {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.startDelay != rhs.startDelay {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.betweenDelay != rhs.betweenDelay {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.messages.count != rhs.messages.count {
|
if lhs.messages.count != rhs.messages.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -401,15 +442,17 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
init(queue: Queue, account: Account, peerId: PeerId) {
|
init(queue: Queue, account: Account, peerId: EnginePeer.Id, messageId: EngineMessage.Id?) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
|
self.messageId = messageId
|
||||||
|
|
||||||
let accountPeerId = account.peerId
|
let accountPeerId = account.peerId
|
||||||
|
|
||||||
self.stateValue = State(interPostInterval: nil, messages: [])
|
self.stateValue = State(interPostInterval: nil, messages: [])
|
||||||
|
|
||||||
|
if messageId == nil {
|
||||||
self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId)
|
self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId)
|
||||||
|> mapToSignal { cachedState -> Signal<State, NoError> in
|
|> mapToSignal { cachedState -> Signal<State, NoError> in
|
||||||
if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
|
if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
|
||||||
@ -422,27 +465,32 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
return .single(State(interPostInterval: nil, messages: []))
|
return .single(State(interPostInterval: nil, messages: []))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let signal: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> = account.postbox.transaction { transaction -> Api.InputPeer? in
|
let signal: Signal<(interPostInterval: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Message]), NoError> = account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
}
|
}
|
||||||
|> mapToSignal { inputPeer -> Signal<(interPostInterval: Int32?, messages: [Message]), NoError> in
|
|> mapToSignal { inputPeer -> Signal<(interPostInterval: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Message]), NoError> in
|
||||||
guard let inputPeer else {
|
guard let inputPeer else {
|
||||||
return .single((nil, []))
|
return .single((nil, nil, nil, []))
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.messages.getSponsoredMessages(peer: inputPeer))
|
var flags: Int32 = 0
|
||||||
|
if let _ = messageId {
|
||||||
|
flags |= (1 << 0)
|
||||||
|
}
|
||||||
|
return account.network.request(Api.functions.messages.getSponsoredMessages(flags: flags, peer: inputPeer, msgId: messageId?.id))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.messages.SponsoredMessages?, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.SponsoredMessages?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<(interPostInterval: Int32?, messages: [Message]), NoError> in
|
|> mapToSignal { result -> Signal<(interPostInterval: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Message]), NoError> in
|
||||||
guard let result = result else {
|
guard let result = result else {
|
||||||
return .single((nil, []))
|
return .single((nil, nil, nil, []))
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.postbox.transaction { transaction -> (interPostInterval: Int32?, messages: [Message]) in
|
return account.postbox.transaction { transaction -> (interPostInterval: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Message]) in
|
||||||
switch result {
|
switch result {
|
||||||
case let .sponsoredMessages(_, postsBetween, messages, chats, users):
|
case let .sponsoredMessages(_, postsBetween, startDelay, betweenDelay, messages, chats, users):
|
||||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||||
|
|
||||||
@ -450,7 +498,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
for message in messages {
|
for message in messages {
|
||||||
switch message {
|
switch message {
|
||||||
case let .sponsoredMessage(flags, randomId, url, title, message, entities, photo, media, color, buttonText, sponsorInfo, additionalInfo):
|
case let .sponsoredMessage(flags, randomId, url, title, message, entities, photo, media, color, buttonText, sponsorInfo, additionalInfo, minDisplayDuration, maxDisplayDuration):
|
||||||
var parsedEntities: [MessageTextEntity] = []
|
var parsedEntities: [MessageTextEntity] = []
|
||||||
if let entities = entities {
|
if let entities = entities {
|
||||||
parsedEntities = messageTextEntitiesFromApiEntities(entities)
|
parsedEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
@ -486,29 +534,33 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
buttonText: buttonText,
|
buttonText: buttonText,
|
||||||
sponsorInfo: sponsorInfo,
|
sponsorInfo: sponsorInfo,
|
||||||
additionalInfo: additionalInfo,
|
additionalInfo: additionalInfo,
|
||||||
canReport: canReport
|
canReport: canReport,
|
||||||
|
minDisplayDuration: minDisplayDuration,
|
||||||
|
maxDisplayDuration: maxDisplayDuration
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if messageId == nil {
|
||||||
CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), interPostInterval: postsBetween, messages: parsedMessages))
|
CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), interPostInterval: postsBetween, messages: parsedMessages))
|
||||||
|
}
|
||||||
|
|
||||||
return (postsBetween, parsedMessages.compactMap { message -> Message? in
|
return (postsBetween, startDelay, betweenDelay, parsedMessages.compactMap { message -> Message? in
|
||||||
return message.toMessage(peerId: peerId, transaction: transaction)
|
return message.toMessage(peerId: peerId, transaction: transaction)
|
||||||
})
|
})
|
||||||
case .sponsoredMessagesEmpty:
|
case .sponsoredMessagesEmpty:
|
||||||
return (nil, [])
|
return (nil, nil, nil, [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disposable.set((signal
|
self.disposable.set((signal
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] interPostInterval, messages in
|
|> deliverOn(self.queue)).start(next: { [weak self] interPostInterval, startDelay, betweenDelay, messages in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.stateValue = State(interPostInterval: interPostInterval, messages: messages)
|
strongSelf.stateValue = State(interPostInterval: interPostInterval, startDelay: startDelay, betweenDelay: betweenDelay, messages: messages)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,14 +612,15 @@ public class AdMessagesHistoryContext {
|
|||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
private let impl: QueueLocalObject<AdMessagesHistoryContextImpl>
|
private let impl: QueueLocalObject<AdMessagesHistoryContextImpl>
|
||||||
public let peerId: EnginePeer.Id
|
public let peerId: EnginePeer.Id
|
||||||
|
public let messageId: EngineMessage.Id?
|
||||||
|
|
||||||
public var state: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> {
|
public var state: Signal<(interPostInterval: Int32?, messages: [Message], startDelay: Int32?, betweenDelay: Int32?), NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
let stateDisposable = impl.state.get().start(next: { state in
|
let stateDisposable = impl.state.get().start(next: { state in
|
||||||
subscriber.putNext((state.interPostInterval, state.messages))
|
subscriber.putNext((state.interPostInterval, state.messages, state.startDelay, state.betweenDelay))
|
||||||
})
|
})
|
||||||
disposable.set(stateDisposable)
|
disposable.set(stateDisposable)
|
||||||
}
|
}
|
||||||
@ -576,11 +629,13 @@ public class AdMessagesHistoryContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(account: Account, peerId: PeerId) {
|
public init(account: Account, peerId: EnginePeer.Id, messageId: EngineMessage.Id? = nil) {
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
|
self.messageId = messageId
|
||||||
|
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return AdMessagesHistoryContextImpl(queue: queue, account: account, peerId: peerId)
|
return AdMessagesHistoryContextImpl(queue: queue, account: account, peerId: peerId, messageId: messageId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ public enum EngineMedia: Equatable {
|
|||||||
case giveaway(TelegramMediaGiveaway)
|
case giveaway(TelegramMediaGiveaway)
|
||||||
case giveawayResults(TelegramMediaGiveawayResults)
|
case giveawayResults(TelegramMediaGiveawayResults)
|
||||||
case paidContent(TelegramMediaPaidContent)
|
case paidContent(TelegramMediaPaidContent)
|
||||||
|
case todo(TelegramMediaTodo)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension EngineMedia {
|
public extension EngineMedia {
|
||||||
@ -59,6 +60,8 @@ public extension EngineMedia {
|
|||||||
return giveawayResults.id
|
return giveawayResults.id
|
||||||
case let .paidContent(paidContent):
|
case let .paidContent(paidContent):
|
||||||
return paidContent.id
|
return paidContent.id
|
||||||
|
case .todo:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +103,8 @@ public extension EngineMedia {
|
|||||||
self = .giveawayResults(giveawayResults)
|
self = .giveawayResults(giveawayResults)
|
||||||
case let paidContent as TelegramMediaPaidContent:
|
case let paidContent as TelegramMediaPaidContent:
|
||||||
self = .paidContent(paidContent)
|
self = .paidContent(paidContent)
|
||||||
|
case let todo as TelegramMediaTodo:
|
||||||
|
self = .todo(todo)
|
||||||
default:
|
default:
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
@ -141,6 +146,8 @@ public extension EngineMedia {
|
|||||||
return giveawayResults
|
return giveawayResults
|
||||||
case let .paidContent(paidContent):
|
case let .paidContent(paidContent):
|
||||||
return paidContent
|
return paidContent
|
||||||
|
case let .todo(todo):
|
||||||
|
return todo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,14 @@ public extension TelegramEngine {
|
|||||||
return PollResultsContext(account: self.account, messageId: messageId, poll: poll)
|
return PollResultsContext(account: self.account, messageId: messageId, poll: poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func requestUpdateTodoMessageItems(messageId: MessageId, completedIds: [Int32], incompletedIds: [Int32]) -> Signal<Never, RequestUpdateTodoMessageError> {
|
||||||
|
return _internal_requestUpdateTodoMessageItems(account: self.account, messageId: messageId, completedIds: completedIds, incompletedIds: incompletedIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func appendTodoMessageItems(messageId: MessageId, items: [TelegramMediaTodo.Item]) -> Signal<Never, AppendTodoMessageError> {
|
||||||
|
return _internal_appendTodoMessageItems(account: self.account, messageId: messageId, items: items)
|
||||||
|
}
|
||||||
|
|
||||||
public func earliestUnseenPersonalMentionMessage(peerId: PeerId, threadId: Int64?) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
|
public func earliestUnseenPersonalMentionMessage(peerId: PeerId, threadId: Int64?) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
|
||||||
let account = self.account
|
let account = self.account
|
||||||
return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId, threadId: threadId)
|
return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId, threadId: threadId)
|
||||||
@ -407,8 +415,8 @@ public extension TelegramEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func adMessages(peerId: PeerId) -> AdMessagesHistoryContext {
|
public func adMessages(peerId: PeerId, messageId: EngineMessage.Id? = nil) -> AdMessagesHistoryContext {
|
||||||
return AdMessagesHistoryContext(account: self.account, peerId: peerId)
|
return AdMessagesHistoryContext(account: self.account, peerId: peerId, messageId: messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageReadStats(id: MessageId) -> Signal<MessageReadStats?, NoError> {
|
public func messageReadStats(id: MessageId) -> Signal<MessageReadStats?, NoError> {
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import Foundation
|
||||||
|
import TelegramApi
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
import MtProtoKit
|
||||||
|
|
||||||
|
|
||||||
|
public enum RequestUpdateTodoMessageError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_requestUpdateTodoMessageItems(account: Account, messageId: MessageId, completedIds: [Int32], incompletedIds: [Int32]) -> Signal<Never, RequestUpdateTodoMessageError> {
|
||||||
|
return account.postbox.transaction { transaction -> Signal<Never, RequestUpdateTodoMessageError> in
|
||||||
|
guard let peer = transaction.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
|
var media: [Media] = []
|
||||||
|
if let todo = currentMessage.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo {
|
||||||
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
|
var updatedCompletions = todo.completions
|
||||||
|
for id in completedIds {
|
||||||
|
updatedCompletions.append(TelegramMediaTodo.Completion(id: id, date: timestamp, completedBy: account.peerId))
|
||||||
|
}
|
||||||
|
updatedCompletions.removeAll(where: { incompletedIds.contains($0.id) })
|
||||||
|
media = [todo.withUpdated(completions: updatedCompletions)]
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media))
|
||||||
|
})
|
||||||
|
return account.network.request(Api.functions.messages.toggleTodoCompleted(peer: inputPeer, msgId: messageId.id, completed: completedIds, incompleted: incompletedIds))
|
||||||
|
|> mapError { _ -> RequestUpdateTodoMessageError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> map { result in
|
||||||
|
account.stateManager.addUpdates(result)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|> castError(RequestUpdateTodoMessageError.self)
|
||||||
|
|> switchToLatest
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AppendTodoMessageError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_appendTodoMessageItems(account: Account, messageId: MessageId, items: [TelegramMediaTodo.Item]) -> Signal<Never, AppendTodoMessageError> {
|
||||||
|
return account.postbox.transaction { transaction -> Signal<Never, AppendTodoMessageError> in
|
||||||
|
guard let peer = transaction.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
|
var media: [Media] = []
|
||||||
|
if let todo = currentMessage.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo {
|
||||||
|
var updatedItems = todo.items
|
||||||
|
updatedItems.append(contentsOf: items)
|
||||||
|
media = [todo.withUpdated(items: updatedItems)]
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media))
|
||||||
|
})
|
||||||
|
return account.network.request(Api.functions.messages.appendTodoList(peer: inputPeer, msgId: messageId.id, list: items.map { $0.apiItem }))
|
||||||
|
|> mapError { _ -> AppendTodoMessageError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> map { result in
|
||||||
|
account.stateManager.addUpdates(result)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|> castError(AppendTodoMessageError.self)
|
||||||
|
|> switchToLatest
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
@ -168,6 +168,11 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
case chatBubbleFileCloudFetchedIncomingIcon
|
case chatBubbleFileCloudFetchedIncomingIcon
|
||||||
case chatBubbleFileCloudFetchedOutgoingIcon
|
case chatBubbleFileCloudFetchedOutgoingIcon
|
||||||
|
|
||||||
|
case chatBubbleTodoDotIncomingIcon
|
||||||
|
case chatBubbleTodoDotOutgoingIcon
|
||||||
|
case chatBubbleTodoCheckIncomingIcon
|
||||||
|
case chatBubbleTodoCheckOutgoingIcon
|
||||||
|
|
||||||
case chatBubbleReplyThumbnailPlayImage
|
case chatBubbleReplyThumbnailPlayImage
|
||||||
|
|
||||||
case chatBubbleDeliveryFailedIcon
|
case chatBubbleDeliveryFailedIcon
|
||||||
|
@ -1402,4 +1402,28 @@ public struct PresentationResourcesChat {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func chatBubbleTodoDotIncomingIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatBubbleTodoDotIncomingIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TodoDot"), color: theme.chat.message.incoming.accentTextColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func chatBubbleTodoDotOutgoingIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatBubbleTodoDotOutgoingIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TodoDot"), color: theme.chat.message.outgoing.accentTextColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func chatBubbleTodoCheckIncomingIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatBubbleTodoCheckIncomingIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TodoCheck"), color: theme.chat.message.incoming.accentTextColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func chatBubbleTodoCheckOutgoingIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatBubbleTodoCheckOutgoingIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TodoCheck"), color: theme.chat.message.outgoing.accentTextColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1286,6 +1286,80 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
}
|
}
|
||||||
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||||
}
|
}
|
||||||
|
case let .todoCompletions(completed, incompleted):
|
||||||
|
var todo: TelegramMediaTodo?
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||||
|
for media in message.media {
|
||||||
|
if let media = media as? TelegramMediaTodo {
|
||||||
|
todo = media
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let todo {
|
||||||
|
if message.author?.id == accountPeerId {
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if let completedTaskId = completed.first, let completedTask = todo.items.first(where: { $0.id == completedTaskId }) {
|
||||||
|
resultString = strings.Notification_TodoCompletedYou(completedTask.text)
|
||||||
|
} else if let incompletedTaskId = incompleted.first, let incompletedTask = todo.items.first(where: { $0.id == incompletedTaskId }) {
|
||||||
|
resultString = strings.Notification_TodoIncompletedYou(incompletedTask.text)
|
||||||
|
} else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||||
|
} else {
|
||||||
|
let peerName = message.author?.compactDisplayTitle ?? ""
|
||||||
|
|
||||||
|
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
||||||
|
attributes[1] = boldAttributes
|
||||||
|
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if let completedTaskId = completed.first, let completedTask = todo.items.first(where: { $0.id == completedTaskId }) {
|
||||||
|
resultString = strings.Notification_TodoCompleted(peerName, completedTask.text)
|
||||||
|
} else if let incompletedTaskId = incompleted.first, let incompletedTask = todo.items.first(where: { $0.id == incompletedTaskId }) {
|
||||||
|
resultString = strings.Notification_TodoIncompleted(peerName, incompletedTask.text)
|
||||||
|
} else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributedString = NSAttributedString(string: ".")
|
||||||
|
}
|
||||||
|
case let .todoAppendTasks(tasks):
|
||||||
|
var todoTitle = ""
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||||
|
for media in message.media {
|
||||||
|
if let todo = media as? TelegramMediaTodo {
|
||||||
|
todoTitle = todo.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if message.author?.id == accountPeerId {
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if tasks.count == 1, let task = tasks.first {
|
||||||
|
resultString = strings.Notification_TodoAddedTaskYou(task.text, todoTitle)
|
||||||
|
} else {
|
||||||
|
resultString = strings.Notification_TodoAddedMultipleTasksYou(strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
|
||||||
|
} else {
|
||||||
|
let peerName = message.author?.compactDisplayTitle ?? ""
|
||||||
|
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
||||||
|
attributes[1] = boldAttributes
|
||||||
|
attributes[2] = boldAttributes
|
||||||
|
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if tasks.count == 1, let task = tasks.first {
|
||||||
|
resultString = strings.Notification_TodoAddedTask(peerName, task.text, todoTitle)
|
||||||
|
} else {
|
||||||
|
resultString = strings.Notification_TodoAddedMultipleTasks(peerName, strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||||
|
}
|
||||||
case .unknown:
|
case .unknown:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
}
|
}
|
||||||
|
@ -481,6 +481,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel",
|
"//submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel",
|
||||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||||
|
"//submodules/TelegramUI/Components/ComposeTodoScreen",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
@ -51,6 +51,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode",
|
||||||
|
@ -41,6 +41,7 @@ import ChatMessageInteractiveFileNode
|
|||||||
import ChatMessageFileBubbleContentNode
|
import ChatMessageFileBubbleContentNode
|
||||||
import ChatMessageWebpageBubbleContentNode
|
import ChatMessageWebpageBubbleContentNode
|
||||||
import ChatMessagePollBubbleContentNode
|
import ChatMessagePollBubbleContentNode
|
||||||
|
import ChatMessageTodoBubbleContentNode
|
||||||
import ChatMessageItem
|
import ChatMessageItem
|
||||||
import ChatMessageItemView
|
import ChatMessageItemView
|
||||||
import ChatMessageSwipeToReplyNode
|
import ChatMessageSwipeToReplyNode
|
||||||
@ -269,6 +270,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
} else if let _ = media as? TelegramMediaPoll {
|
} else if let _ = media as? TelegramMediaPoll {
|
||||||
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
|
} else if let _ = media as? TelegramMediaTodo {
|
||||||
|
result.append((message, ChatMessageTodoBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
|
needReactions = false
|
||||||
} else if let _ = media as? TelegramMediaGiveaway {
|
} else if let _ = media as? TelegramMediaGiveaway {
|
||||||
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
|
@ -449,10 +449,12 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
if "".isEmpty, let contentNode = strongSelf.supernode as? ChatMessagePollBubbleContentNode, let backdropNode = contentNode.bubbleBackgroundNode?.backdropNode {
|
if let theme = strongSelf.theme, theme.overallDarkAppearance, let contentNode = strongSelf.supernode as? ChatMessagePollBubbleContentNode, let backdropNode = contentNode.bubbleBackgroundNode?.backdropNode {
|
||||||
strongSelf.highlightedBackgroundNode.layer.compositingFilter = "overlayBlendMode"
|
strongSelf.highlightedBackgroundNode.layer.compositingFilter = "overlayBlendMode"
|
||||||
strongSelf.highlightedBackgroundNode.frame = strongSelf.view.convert(strongSelf.highlightedBackgroundNode.frame, to: backdropNode.view)
|
strongSelf.highlightedBackgroundNode.frame = strongSelf.view.convert(strongSelf.highlightedBackgroundNode.frame, to: backdropNode.view)
|
||||||
backdropNode.addSubnode(strongSelf.highlightedBackgroundNode)
|
backdropNode.addSubnode(strongSelf.highlightedBackgroundNode)
|
||||||
|
} else {
|
||||||
|
strongSelf.insertSubnode(strongSelf.highlightedBackgroundNode, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ChatMessageTodoBubbleContentNode",
|
||||||
|
module_name = "ChatMessageTodoBubbleContentNode",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/AsyncDisplayKit",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/Postbox",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/UrlEscaping",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/AvatarNode",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ChatMessageBackground",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||||
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
@ -170,6 +170,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
}, openBoostToUnrestrict: {
|
}, openBoostToUnrestrict: {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
|
}, editTodoMessage: { _, _ in
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
@ -647,6 +647,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
|
@ -502,6 +502,8 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))
|
||||||
|
|
||||||
|
@ -281,7 +281,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
public let playShakeAnimation: () -> Void
|
public let playShakeAnimation: () -> Void
|
||||||
public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void
|
public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void
|
||||||
public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void
|
public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void
|
||||||
|
public let requestToggleTodoMessageItem: (MessageId, Int32, Bool) -> Void
|
||||||
|
public let displayTodoToggleUnavailable: (MessageId) -> Void
|
||||||
public var canPlayMedia: Bool = false
|
public var canPlayMedia: Bool = false
|
||||||
public var hiddenMedia: [MessageId: [Media]] = [:]
|
public var hiddenMedia: [MessageId: [Media]] = [:]
|
||||||
public var expandedTranslationMessageStableIds: Set<UInt32> = Set()
|
public var expandedTranslationMessageStableIds: Set<UInt32> = Set()
|
||||||
@ -444,6 +445,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
playShakeAnimation: @escaping () -> Void,
|
playShakeAnimation: @escaping () -> Void,
|
||||||
displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void,
|
displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void,
|
||||||
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
||||||
|
requestToggleTodoMessageItem: @escaping (MessageId, Int32, Bool) -> Void,
|
||||||
|
displayTodoToggleUnavailable: @escaping (MessageId) -> Void,
|
||||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||||
pollActionState: ChatInterfacePollActionState,
|
pollActionState: ChatInterfacePollActionState,
|
||||||
stickerSettings: ChatInterfaceStickerSettings,
|
stickerSettings: ChatInterfaceStickerSettings,
|
||||||
@ -563,6 +566,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
self.playShakeAnimation = playShakeAnimation
|
self.playShakeAnimation = playShakeAnimation
|
||||||
self.displayQuickShare = displayQuickShare
|
self.displayQuickShare = displayQuickShare
|
||||||
self.updateChatLocationThread = updateChatLocationThread
|
self.updateChatLocationThread = updateChatLocationThread
|
||||||
|
self.requestToggleTodoMessageItem = requestToggleTodoMessageItem
|
||||||
|
self.displayTodoToggleUnavailable = displayTodoToggleUnavailable
|
||||||
|
|
||||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||||
|
|
||||||
|
51
submodules/TelegramUI/Components/ComposeTodoScreen/BUILD
Normal file
51
submodules/TelegramUI/Components/ComposeTodoScreen/BUILD
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ComposeTodoScreen",
|
||||||
|
module_name = "ComposeTodoScreen",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/AsyncDisplayKit",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ItemListUI",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/AlertUI",
|
||||||
|
"//submodules/PresentationDataUtils",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/ObjCRuntimeUtils",
|
||||||
|
"//submodules/AttachmentUI",
|
||||||
|
"//submodules/TextInputMenu",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/Components/ViewControllerComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||||
|
"//submodules/AppBundle",
|
||||||
|
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||||
|
"//submodules/UndoUI",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PeerAllowedReactionsScreen",
|
||||||
|
"//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListComposePollOptionComponent",
|
||||||
|
"//submodules/ComposePollUI",
|
||||||
|
"//submodules/Markdown",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "ListComposePollOptionComponent",
|
||||||
|
module_name = "ListComposePollOptionComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/TelegramUI/Components/SliderComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
|
"//submodules/CheckNode",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/PresentationDataUtils",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -73,6 +73,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let placeholder: NSAttributedString?
|
public let placeholder: NSAttributedString?
|
||||||
|
public let isEnabled: Bool
|
||||||
public let resetText: ResetText?
|
public let resetText: ResetText?
|
||||||
public let assumeIsEditing: Bool
|
public let assumeIsEditing: Bool
|
||||||
public let characterLimit: Int?
|
public let characterLimit: Int?
|
||||||
@ -92,6 +93,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
placeholder: NSAttributedString? = nil,
|
placeholder: NSAttributedString? = nil,
|
||||||
|
isEnabled: Bool = true,
|
||||||
resetText: ResetText? = nil,
|
resetText: ResetText? = nil,
|
||||||
assumeIsEditing: Bool = false,
|
assumeIsEditing: Bool = false,
|
||||||
characterLimit: Int,
|
characterLimit: Int,
|
||||||
@ -110,6 +112,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
self.isEnabled = isEnabled
|
||||||
self.resetText = resetText
|
self.resetText = resetText
|
||||||
self.assumeIsEditing = assumeIsEditing
|
self.assumeIsEditing = assumeIsEditing
|
||||||
self.characterLimit = characterLimit
|
self.characterLimit = characterLimit
|
||||||
@ -140,6 +143,9 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
if lhs.placeholder != rhs.placeholder {
|
if lhs.placeholder != rhs.placeholder {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isEnabled != rhs.isEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.resetText != rhs.resetText {
|
if lhs.resetText != rhs.resetText {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -400,6 +406,9 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
self.textField.parentState = state
|
self.textField.parentState = state
|
||||||
}
|
}
|
||||||
transition.setFrame(view: textFieldView, frame: textFieldFrame)
|
transition.setFrame(view: textFieldView, frame: textFieldFrame)
|
||||||
|
|
||||||
|
transition.setAlpha(view: textFieldView, alpha: component.isEnabled ? 1.0 : 0.3)
|
||||||
|
textFieldView.isUserInteractionEnabled = component.isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
if let selection = component.selection {
|
if let selection = component.selection {
|
@ -76,6 +76,7 @@ public final class ListSectionContentView: UIView {
|
|||||||
private let contentItemContainerView: UIView
|
private let contentItemContainerView: UIView
|
||||||
|
|
||||||
public let externalContentBackgroundView: DynamicCornerRadiusView
|
public let externalContentBackgroundView: DynamicCornerRadiusView
|
||||||
|
public var automaticallyLayoutExternalContentBackgroundView = true
|
||||||
|
|
||||||
public var itemViews: [AnyHashable: ItemView] = [:]
|
public var itemViews: [AnyHashable: ItemView] = [:]
|
||||||
private var highlightedItemId: AnyHashable?
|
private var highlightedItemId: AnyHashable?
|
||||||
@ -283,7 +284,9 @@ public final class ListSectionContentView: UIView {
|
|||||||
}
|
}
|
||||||
self.externalContentBackgroundView.update(size: backgroundFrame.size, corners: corners, transition: transition)
|
self.externalContentBackgroundView.update(size: backgroundFrame.size, corners: corners, transition: transition)
|
||||||
}
|
}
|
||||||
|
if self.automaticallyLayoutExternalContentBackgroundView {
|
||||||
transition.setFrame(view: self.externalContentBackgroundView, frame: backgroundFrame)
|
transition.setFrame(view: self.externalContentBackgroundView, frame: backgroundFrame)
|
||||||
|
}
|
||||||
transition.setAlpha(view: self.externalContentBackgroundView, alpha: backgroundAlpha)
|
transition.setAlpha(view: self.externalContentBackgroundView, alpha: backgroundAlpha)
|
||||||
transition.setCornerRadius(layer: self.layer, cornerRadius: contentCornerRadius)
|
transition.setCornerRadius(layer: self.layer, cornerRadius: contentCornerRadius)
|
||||||
|
|
||||||
|
@ -435,6 +435,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
}, openBoostToUnrestrict: {
|
}, openBoostToUnrestrict: {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
|
}, editTodoMessage: { _, _ in
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
@ -3847,6 +3848,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in
|
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in
|
||||||
|
@ -823,6 +823,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}, openBoostToUnrestrict: {
|
}, openBoostToUnrestrict: {
|
||||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||||
}, dismissAllTooltips: {
|
}, dismissAllTooltips: {
|
||||||
|
}, editTodoMessage: { _, _ in
|
||||||
}, updateHistoryFilter: { _ in
|
}, updateHistoryFilter: { _ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
}, toggleChatSidebarMode: {
|
}, toggleChatSidebarMode: {
|
||||||
|
@ -835,11 +835,21 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
))
|
))
|
||||||
} else if case .unique = giftAnimationSubject {
|
} else if case .unique = giftAnimationSubject {
|
||||||
|
let reason: String
|
||||||
|
if count < StarsAmount.zero, case let .transaction(transaction, _) = subject {
|
||||||
|
if transaction.flags.contains(.isStarGiftResale) {
|
||||||
|
reason = strings.Stars_Transaction_GiftPurchase
|
||||||
|
} else {
|
||||||
|
reason = strings.Stars_Transaction_GiftTransfer
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reason = strings.Stars_Transaction_GiftSale
|
||||||
|
}
|
||||||
tableItems.append(.init(
|
tableItems.append(.init(
|
||||||
id: "reason",
|
id: "reason",
|
||||||
title: strings.Stars_Transaction_Giveaway_Reason,
|
title: strings.Stars_Transaction_Giveaway_Reason,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
MultilineTextComponent(text: .plain(NSAttributedString(string: count < StarsAmount.zero ? strings.Stars_Transaction_GiftPurchase : strings.Stars_Transaction_GiftSale, font: tableFont, textColor: tableTextColor)))
|
MultilineTextComponent(text: .plain(NSAttributedString(string: reason, font: tableFont, textColor: tableTextColor)))
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,15 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_GiftSale : environment.strings.Stars_Intro_Transaction_GiftPurchase
|
if item.count > StarsAmount.zero {
|
||||||
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftSale
|
||||||
|
} else {
|
||||||
|
if item.flags.contains(.isStarGiftResale) {
|
||||||
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftPurchase
|
||||||
|
} else {
|
||||||
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftTransfer
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let _ = item.giveawayMessageId {
|
} else if let _ = item.giveawayMessageId {
|
||||||
|
@ -9,17 +9,20 @@ public final class ToastContentComponent: Component {
|
|||||||
public let content: AnyComponent<Empty>
|
public let content: AnyComponent<Empty>
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let iconSpacing: CGFloat
|
public let iconSpacing: CGFloat
|
||||||
|
public let action: (() -> Void)?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
icon: AnyComponent<Empty>,
|
icon: AnyComponent<Empty>,
|
||||||
content: AnyComponent<Empty>,
|
content: AnyComponent<Empty>,
|
||||||
insets: UIEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0),
|
insets: UIEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0),
|
||||||
iconSpacing: CGFloat = 10.0
|
iconSpacing: CGFloat = 10.0,
|
||||||
|
action: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.content = content
|
self.content = content
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.iconSpacing = iconSpacing
|
self.iconSpacing = iconSpacing
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ToastContentComponent, rhs: ToastContentComponent) -> Bool {
|
public static func ==(lhs: ToastContentComponent, rhs: ToastContentComponent) -> Bool {
|
||||||
@ -39,6 +42,8 @@ public final class ToastContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
|
private var component: ToastContentComponent?
|
||||||
|
|
||||||
private let backgroundView: BlurredBackgroundView
|
private let backgroundView: BlurredBackgroundView
|
||||||
private let icon = ComponentView<Empty>()
|
private let icon = ComponentView<Empty>()
|
||||||
private let content = ComponentView<Empty>()
|
private let content = ComponentView<Empty>()
|
||||||
@ -63,9 +68,21 @@ public final class ToastContentComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc private func tapped() {
|
||||||
|
self.component?.action?()
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: ToastContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: ToastContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
|
|
||||||
|
if self.component == nil {
|
||||||
|
if let _ = component.action {
|
||||||
|
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.component = component
|
||||||
|
|
||||||
let leftInset: CGFloat = component.insets.left
|
let leftInset: CGFloat = component.insets.left
|
||||||
let rightInset: CGFloat = component.insets.right
|
let rightInset: CGFloat = component.insets.right
|
||||||
let topInset: CGFloat = component.insets.top
|
let topInset: CGFloat = component.insets.top
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Todo.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Todo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "todolist_30.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Todo.imageset/todolist_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Todo.imageset/todolist_30.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoCheck.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoCheck.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "todo_check.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoCheck.imageset/todo_check.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoCheck.imageset/todo_check.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoDot.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoDot.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "todo_dot.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoDot.imageset/todo_dot.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/TodoDot.imageset/todo_dot.pdf
vendored
Normal file
Binary file not shown.
@ -21,7 +21,6 @@ import DeviceLocationManager
|
|||||||
import ShareController
|
import ShareController
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
@ -4148,6 +4147,11 @@ extension ChatControllerImpl {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
}, editTodoMessage: { [weak self] messageId, append in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openTodoEditing(messageId: messageId, append: append)
|
||||||
}, updateHistoryFilter: { [weak self] update in
|
}, updateHistoryFilter: { [weak self] update in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -22,6 +22,7 @@ import ShareController
|
|||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ComposePollUI
|
import ComposePollUI
|
||||||
|
import ComposeTodoScreen
|
||||||
import AlertUI
|
import AlertUI
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import UndoUI
|
import UndoUI
|
||||||
@ -342,6 +343,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var selectMessagePollOptionDisposables: DisposableDict<MessageId>?
|
var selectMessagePollOptionDisposables: DisposableDict<MessageId>?
|
||||||
var selectPollOptionFeedback: HapticFeedback?
|
var selectPollOptionFeedback: HapticFeedback?
|
||||||
|
|
||||||
|
var updateMessageTodoDisposables: DisposableDict<MessageId>?
|
||||||
|
|
||||||
var resolveUrlDisposable: MetaDisposable?
|
var resolveUrlDisposable: MetaDisposable?
|
||||||
|
|
||||||
var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:]
|
var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:]
|
||||||
@ -4789,6 +4792,68 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return direction ? .right : .left
|
return direction ? .right : .left
|
||||||
}
|
}
|
||||||
self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection)
|
self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection)
|
||||||
|
}, requestToggleTodoMessageItem: { [weak self] messageId, itemId, value in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let disposables: DisposableDict<MessageId>
|
||||||
|
if let current = self.updateMessageTodoDisposables {
|
||||||
|
disposables = current
|
||||||
|
} else {
|
||||||
|
disposables = DisposableDict()
|
||||||
|
self.updateMessageTodoDisposables = disposables
|
||||||
|
}
|
||||||
|
var completedIds: [Int32] = []
|
||||||
|
var incompletedIds: [Int32] = []
|
||||||
|
if value {
|
||||||
|
completedIds.append(itemId)
|
||||||
|
} else {
|
||||||
|
incompletedIds.append(itemId)
|
||||||
|
}
|
||||||
|
let signal = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: messageId, completedIds: completedIds, incompletedIds: incompletedIds)
|
||||||
|
disposables.set((signal
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { todo in
|
||||||
|
|
||||||
|
}, error: { _ in
|
||||||
|
|
||||||
|
}), forKey: messageId)
|
||||||
|
}, displayTodoToggleUnavailable: { [weak self] messageId in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dismissAllUndoControllers()
|
||||||
|
//TODO:localize
|
||||||
|
if !self.context.isPremium {
|
||||||
|
let controller = UndoOverlayController(
|
||||||
|
presentationData: self.presentationData,
|
||||||
|
content: .premiumPaywall(title: nil, text: "Only [Telegram Premium]() subscribers can mark tasks as done.", customUndoText: nil, timeout: nil, linkAction: nil),
|
||||||
|
action: { [weak self] action in
|
||||||
|
guard let self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if case .info = action {
|
||||||
|
let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
|
||||||
|
self.push(controller)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.present(controller, in: .current)
|
||||||
|
} else if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||||
|
var peerName = ""
|
||||||
|
if let author = message.author {
|
||||||
|
peerName = EnginePeer(author).compactDisplayTitle
|
||||||
|
}
|
||||||
|
let controller = UndoOverlayController(
|
||||||
|
presentationData: self.presentationData,
|
||||||
|
content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: "\(peerName) has restricted others from editing this to do list.", customUndoText: nil, timeout: nil),
|
||||||
|
action: { _ in
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.present(controller, in: .current)
|
||||||
|
}
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
|
||||||
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
|
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
|
||||||
|
|
||||||
@ -5845,6 +5910,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.updateChatLocationThreadDisposable?.dispose()
|
self.updateChatLocationThreadDisposable?.dispose()
|
||||||
self.accountPeerDisposable?.dispose()
|
self.accountPeerDisposable?.dispose()
|
||||||
self.contentDataDisposable?.dispose()
|
self.contentDataDisposable?.dispose()
|
||||||
|
self.updateMessageTodoDisposables?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
|
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
|
||||||
@ -7577,56 +7643,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.present(tooltipScreen, in: .current)
|
self.present(tooltipScreen, in: .current)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configurePollCreation(isQuiz: Bool? = nil) -> ViewController? {
|
|
||||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return createPollController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), isQuiz: isQuiz, completion: { [weak self] poll in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject
|
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.chatDisplayNode.collapseInput()
|
|
||||||
|
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
||||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, nil)
|
|
||||||
let message: EnqueueMessage = .message(
|
|
||||||
text: "",
|
|
||||||
attributes: [],
|
|
||||||
inlineStickers: [:],
|
|
||||||
mediaReference: .standalone(media: TelegramMediaPoll(
|
|
||||||
pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)),
|
|
||||||
publicity: poll.publicity,
|
|
||||||
kind: poll.kind,
|
|
||||||
text: poll.text.string,
|
|
||||||
textEntities: poll.text.entities,
|
|
||||||
options: poll.options,
|
|
||||||
correctAnswers: poll.correctAnswers,
|
|
||||||
results: poll.results,
|
|
||||||
isClosed: false,
|
|
||||||
deadlineTimeout: poll.deadlineTimeout
|
|
||||||
)),
|
|
||||||
threadId: strongSelf.chatLocation.threadId,
|
|
||||||
replyToMessageId: nil,
|
|
||||||
replyToStoryId: nil,
|
|
||||||
localGroupingKey: nil,
|
|
||||||
correlationId: nil,
|
|
||||||
bubbleUpEmojiOrStickersets: []
|
|
||||||
)
|
|
||||||
strongSelf.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func transformEnqueueMessages(_ messages: [EnqueueMessage], postpone: Bool = false) -> [EnqueueMessage] {
|
func transformEnqueueMessages(_ messages: [EnqueueMessage], postpone: Bool = false) -> [EnqueueMessage] {
|
||||||
let silentPosting = self.presentationInterfaceState.interfaceState.silentPosting
|
let silentPosting = self.presentationInterfaceState.interfaceState.silentPosting
|
||||||
return transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone)
|
return transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone)
|
||||||
|
@ -33,6 +33,8 @@ import AutomaticBusinessMessageSetupScreen
|
|||||||
import MediaEditorScreen
|
import MediaEditorScreen
|
||||||
import CameraScreen
|
import CameraScreen
|
||||||
import ShareController
|
import ShareController
|
||||||
|
import ComposeTodoScreen
|
||||||
|
import ComposePollUI
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
enum AttachMenuSubject {
|
enum AttachMenuSubject {
|
||||||
@ -113,6 +115,8 @@ extension ChatControllerImpl {
|
|||||||
availableButtons.insert(.poll, at: max(0, availableButtons.count - 1))
|
availableButtons.insert(.poll, at: max(0, availableButtons.count - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
availableButtons.append(.todo)
|
||||||
|
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
|
|
||||||
var isScheduledMessages = false
|
var isScheduledMessages = false
|
||||||
@ -622,6 +626,26 @@ extension ChatControllerImpl {
|
|||||||
completion(controller, controller.mediaPickerContext)
|
completion(controller, controller.mediaPickerContext)
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
}
|
}
|
||||||
|
case .todo:
|
||||||
|
if strongSelf.context.isPremium {
|
||||||
|
if let controller = strongSelf.configureTodoCreation() as? AttachmentContainable {
|
||||||
|
completion(controller, controller.mediaPickerContext)
|
||||||
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
|
let demoController = strongSelf.context.sharedContext.makePremiumDemoController(context: strongSelf.context, subject: .todo, forceDark: false, action: {
|
||||||
|
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .todo, forceDark: false, dismissed: nil)
|
||||||
|
replaceImpl?(controller)
|
||||||
|
}, dismissed: nil)
|
||||||
|
replaceImpl = { [weak demoController] c in
|
||||||
|
demoController?.replace(with: c)
|
||||||
|
}
|
||||||
|
strongSelf.push(demoController)
|
||||||
|
Queue.mainQueue().after(0.4) {
|
||||||
|
strongSelf.attachmentController?.dismiss(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
case .gift:
|
case .gift:
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, let starsContext = context.starsContext {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, let starsContext = context.starsContext {
|
||||||
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
||||||
@ -1967,4 +1991,147 @@ extension ChatControllerImpl {
|
|||||||
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
self.push(mainController)
|
self.push(mainController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configurePollCreation(isQuiz: Bool? = nil) -> ViewController? {
|
||||||
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ComposePollScreen(
|
||||||
|
context: self.context,
|
||||||
|
initialData: ComposePollScreen.initialData(context: self.context),
|
||||||
|
peer: EnginePeer(peer),
|
||||||
|
isQuiz: isQuiz,
|
||||||
|
completion: { [weak self] poll in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject
|
||||||
|
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.chatDisplayNode.collapseInput()
|
||||||
|
|
||||||
|
self.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||||
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, nil)
|
||||||
|
let message: EnqueueMessage = .message(
|
||||||
|
text: "",
|
||||||
|
attributes: [],
|
||||||
|
inlineStickers: [:],
|
||||||
|
mediaReference: .standalone(media: TelegramMediaPoll(
|
||||||
|
pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)),
|
||||||
|
publicity: poll.publicity,
|
||||||
|
kind: poll.kind,
|
||||||
|
text: poll.text.string,
|
||||||
|
textEntities: poll.text.entities,
|
||||||
|
options: poll.options,
|
||||||
|
correctAnswers: poll.correctAnswers,
|
||||||
|
results: poll.results,
|
||||||
|
isClosed: false,
|
||||||
|
deadlineTimeout: poll.deadlineTimeout
|
||||||
|
)),
|
||||||
|
threadId: self.chatLocation.threadId,
|
||||||
|
replyToMessageId: nil,
|
||||||
|
replyToStoryId: nil,
|
||||||
|
localGroupingKey: nil,
|
||||||
|
correlationId: nil,
|
||||||
|
bubbleUpEmojiOrStickersets: []
|
||||||
|
)
|
||||||
|
self.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureTodoCreation() -> ViewController? {
|
||||||
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ComposeTodoScreen(
|
||||||
|
context: self.context,
|
||||||
|
initialData: ComposeTodoScreen.initialData(
|
||||||
|
context: self.context
|
||||||
|
),
|
||||||
|
peer: EnginePeer(peer),
|
||||||
|
completion: { [weak self] todo in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject
|
||||||
|
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.chatDisplayNode.collapseInput()
|
||||||
|
|
||||||
|
self.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||||
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, nil)
|
||||||
|
let message: EnqueueMessage = .message(
|
||||||
|
text: "",
|
||||||
|
attributes: [],
|
||||||
|
inlineStickers: [:],
|
||||||
|
mediaReference: .standalone(media: todo),
|
||||||
|
threadId: self.chatLocation.threadId,
|
||||||
|
replyToMessageId: nil,
|
||||||
|
replyToStoryId: nil,
|
||||||
|
localGroupingKey: nil,
|
||||||
|
correlationId: nil,
|
||||||
|
bubbleUpEmojiOrStickersets: []
|
||||||
|
)
|
||||||
|
self.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTodoEditing(messageId: EngineMessage.Id, append: Bool) {
|
||||||
|
guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let existingTodo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
|
||||||
|
|
||||||
|
let controller = ComposeTodoScreen(
|
||||||
|
context: self.context,
|
||||||
|
initialData: ComposeTodoScreen.initialData(
|
||||||
|
context: self.context,
|
||||||
|
existingTodo: existingTodo,
|
||||||
|
append: append,
|
||||||
|
canEdit: canEdit
|
||||||
|
),
|
||||||
|
peer: EnginePeer(peer),
|
||||||
|
completion: { [weak self] todo in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if canEdit {
|
||||||
|
let _ = self.context.engine.messages.requestEditMessage(
|
||||||
|
messageId: messageId,
|
||||||
|
text: "",
|
||||||
|
media: .update(.standalone(media: todo)),
|
||||||
|
entities: nil,
|
||||||
|
inlineStickers: [:]
|
||||||
|
).start()
|
||||||
|
} else {
|
||||||
|
let appendedItems = Array(todo.items[existingTodo.items.count ..< todo.items.count])
|
||||||
|
let _ = self.context.engine.messages.appendTodoMessageItems(messageId: messageId, items: appendedItems).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
controller.navigationPresentation = .modal
|
||||||
|
self.push(controller)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -791,22 +791,22 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
self.prefetchManager = InChatPrefetchManager(context: context)
|
self.prefetchManager = InChatPrefetchManager(context: context)
|
||||||
|
|
||||||
self.adMessagesContext = adMessagesContext
|
self.adMessagesContext = adMessagesContext
|
||||||
var adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>
|
var adMessages: Signal<(interPostInterval: Int32?, messages: [Message], startDelay: Int32?, betweenDelay: Int32?), NoError>
|
||||||
if case .bubbles = mode, let adMessagesContext {
|
if case .bubbles = mode, let adMessagesContext {
|
||||||
let peerId = adMessagesContext.peerId
|
let peerId = adMessagesContext.peerId
|
||||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||||
adMessages = .single((nil, []))
|
adMessages = .single((nil, [], nil, nil))
|
||||||
} else {
|
} else {
|
||||||
if context.sharedContext.immediateExperimentalUISettings.fakeAds {
|
if context.sharedContext.immediateExperimentalUISettings.fakeAds {
|
||||||
adMessages = context.engine.data.get(
|
adMessages = context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||||
)
|
)
|
||||||
|> map { peer -> (interPostInterval: Int32?, messages: [Message]) in
|
|> map { peer -> (interPostInterval: Int32?, messages: [Message], startDelay: Int32?, betweenDelay: Int32?) in
|
||||||
let fakeAdMessages: [Message] = (0 ..< 10).map { i -> Message in
|
let fakeAdMessages: [Message] = (0 ..< 10).map { i -> Message in
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
|
|
||||||
let mappedMessageType: AdMessageAttribute.MessageType = .sponsored
|
let mappedMessageType: AdMessageAttribute.MessageType = .sponsored
|
||||||
attributes.append(AdMessageAttribute(opaqueId: "fake_ad_\(i)".data(using: .utf8)!, messageType: mappedMessageType, url: "t.me/telegram", buttonText: "VIEW", sponsorInfo: nil, additionalInfo: nil, canReport: false, hasContentMedia: false))
|
attributes.append(AdMessageAttribute(opaqueId: "fake_ad_\(i)".data(using: .utf8)!, messageType: mappedMessageType, url: "t.me/telegram", buttonText: "VIEW", sponsorInfo: nil, additionalInfo: nil, canReport: false, hasContentMedia: false, minDisplayDuration: nil, maxDisplayDuration: nil))
|
||||||
|
|
||||||
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
||||||
|
|
||||||
@ -875,14 +875,14 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
associatedStories: [:]
|
associatedStories: [:]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (10, fakeAdMessages)
|
return (10, fakeAdMessages, nil, nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
adMessages = adMessagesContext.state
|
adMessages = adMessagesContext.state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
adMessages = .single((nil, []))
|
adMessages = .single((nil, [], nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
let clientId = Atomic<Int32>(value: nextClientId)
|
let clientId = Atomic<Int32>(value: nextClientId)
|
||||||
@ -1224,9 +1224,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
self.beginChatHistoryTransitions(resetScrolling: true, switchedToAnotherSource: false)
|
self.beginChatHistoryTransitions(resetScrolling: true, switchedToAnotherSource: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func beginAdMessageManagement(adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>) {
|
private func beginAdMessageManagement(adMessages: Signal<(interPostInterval: Int32?, messages: [Message], startDelay: Int32?, betweenDelay: Int32?), NoError>) {
|
||||||
self.adMessagesDisposable = (adMessages
|
self.adMessagesDisposable = (adMessages
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] interPostInterval, messages in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] interPostInterval, messages, _, _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1479,25 +1479,33 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
isMigrated = false
|
isMigrated = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.canEdit && !isPinnedMessages && !isMigrated {
|
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
|
||||||
}, action: { c, f in
|
|
||||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
|
||||||
f(.custom(transition))
|
|
||||||
})
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
var activePoll: TelegramMediaPoll?
|
var activePoll: TelegramMediaPoll?
|
||||||
|
var activeTodo: TelegramMediaTodo?
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
if let poll = media as? TelegramMediaPoll, !poll.isClosed, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll {
|
if let poll = media as? TelegramMediaPoll, !poll.isClosed, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll {
|
||||||
if !isPollEffectivelyClosed(message: message, poll: poll) {
|
if !isPollEffectivelyClosed(message: message, poll: poll) {
|
||||||
activePoll = poll
|
activePoll = poll
|
||||||
}
|
}
|
||||||
|
} else if let todo = media as? TelegramMediaTodo {
|
||||||
|
activeTodo = todo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.canEdit && !isPinnedMessages && !isMigrated {
|
||||||
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { c, f in
|
||||||
|
if let _ = activeTodo {
|
||||||
|
interfaceInteraction.editTodoMessage(messages[0].id, false)
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
} else {
|
||||||
|
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||||
|
f(.custom(transition))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
if let activePoll = activePoll, let voters = activePoll.results.voters {
|
if let activePoll = activePoll, let voters = activePoll.results.voters {
|
||||||
var hasSelected = false
|
var hasSelected = false
|
||||||
for result in voters {
|
for result in voters {
|
||||||
@ -1515,6 +1523,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let activeTodo {
|
||||||
|
var canAppend = false
|
||||||
|
if message.author?.id == context.account.peerId || activeTodo.flags.contains(.othersCanAppend) {
|
||||||
|
canAppend = true
|
||||||
|
}
|
||||||
|
if canAppend {
|
||||||
|
actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
interfaceInteraction.editTodoMessage(messages[0].id, true)
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var canPin = data.canPin
|
var canPin = data.canPin
|
||||||
if case let .replyThread(message) = chatPresentationInterfaceState.chatLocation {
|
if case let .replyThread(message) = chatPresentationInterfaceState.chatLocation {
|
||||||
if !message.isForumPost {
|
if !message.isForumPost {
|
||||||
|
@ -194,6 +194,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
||||||
|
|
||||||
self.dimNode = ASDisplayNode()
|
self.dimNode = ASDisplayNode()
|
||||||
|
@ -2400,6 +2400,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, playShakeAnimation: {
|
}, playShakeAnimation: {
|
||||||
}, displayQuickShare: { _, _ ,_ in
|
}, displayQuickShare: { _, _ ,_ in
|
||||||
}, updateChatLocationThread: { _, _ in
|
}, updateChatLocationThread: { _, _ in
|
||||||
|
}, requestToggleTodoMessageItem: { _, _, _ in
|
||||||
|
}, displayTodoToggleUnavailable: { _ in
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))
|
||||||
|
|
||||||
@ -2715,6 +2717,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mappedSource = .animatedEmoji
|
mappedSource = .animatedEmoji
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
mappedSource = .paidMessages
|
mappedSource = .paidMessages
|
||||||
|
case .todo:
|
||||||
|
mappedSource = .paidMessages
|
||||||
case let .auth(price):
|
case let .auth(price):
|
||||||
mappedSource = .auth(price)
|
mappedSource = .auth(price)
|
||||||
}
|
}
|
||||||
@ -2793,6 +2797,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mappedSubject = .messageEffects
|
mappedSubject = .messageEffects
|
||||||
case .paidMessages:
|
case .paidMessages:
|
||||||
mappedSubject = .paidMessages
|
mappedSubject = .paidMessages
|
||||||
|
case .todo:
|
||||||
|
mappedSubject = .todo
|
||||||
case .business:
|
case .business:
|
||||||
mappedSubject = .business
|
mappedSubject = .business
|
||||||
buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton
|
buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton
|
||||||
|
Loading…
x
Reference in New Issue
Block a user