mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Initial ad messages implementation
This commit is contained in:
parent
01d708ef4d
commit
44562a845a
@ -443,7 +443,7 @@ public protocol PresentationGroupCall: AnyObject {
|
||||
func playTone(_ tone: PresentationGroupCallTone)
|
||||
|
||||
func updateMuteState(peerId: PeerId, isMuted: Bool) -> GroupCallParticipantsContext.Participant.MuteState?
|
||||
func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?)
|
||||
func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?)
|
||||
|
||||
func updateTitle(_ title: String)
|
||||
|
||||
|
@ -71,6 +71,10 @@ final class ItemCacheTable: Table {
|
||||
self.valueBox.set(self.table, key: self.itemKey(id: id), value: encoder.readBufferNoCopy())
|
||||
})
|
||||
}
|
||||
|
||||
func putData(id: ItemCacheEntryId, entry: Data, metaTable: ItemCacheMetaTable) {
|
||||
self.valueBox.set(self.table, key: self.itemKey(id: id), value: ReadBuffer(data: entry))
|
||||
}
|
||||
|
||||
func retrieve(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) -> PostboxCoding? {
|
||||
if let value = self.valueBox.get(self.table, key: self.itemKey(id: id)), let entry = PostboxDecoder(buffer: value).decodeRootObject() {
|
||||
@ -78,6 +82,13 @@ final class ItemCacheTable: Table {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func retrieveData(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) -> Data? {
|
||||
if let value = self.valueBox.get(self.table, key: self.itemKey(id: id)) {
|
||||
return value.makeData()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func remove(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) {
|
||||
self.valueBox.remove(self.table, key: self.itemKey(id: id), secure: false)
|
||||
|
@ -712,6 +712,11 @@ public final class Transaction {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.putItemCacheEntry(id: id, entry: entry, collectionSpec: collectionSpec)
|
||||
}
|
||||
|
||||
public func putItemCacheEntryData(id: ItemCacheEntryId, entry: Data, collectionSpec: ItemCacheCollectionSpec) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.putItemCacheEntryData(id: id, entry: entry, collectionSpec: collectionSpec)
|
||||
}
|
||||
|
||||
public func removeItemCacheEntry(id: ItemCacheEntryId) {
|
||||
assert(!self.disposed)
|
||||
@ -722,6 +727,11 @@ public final class Transaction {
|
||||
assert(!self.disposed)
|
||||
return self.postbox?.retrieveItemCacheEntry(id: id)
|
||||
}
|
||||
|
||||
public func retrieveItemCacheEntryData(id: ItemCacheEntryId) -> Data? {
|
||||
assert(!self.disposed)
|
||||
return self.postbox?.retrieveItemCacheEntryData(id: id)
|
||||
}
|
||||
|
||||
public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
||||
assert(!self.disposed)
|
||||
@ -2332,10 +2342,19 @@ public final class Postbox {
|
||||
self.itemCacheTable.put(id: id, entry: entry, metaTable: self.itemCacheMetaTable)
|
||||
self.currentUpdatedCacheEntryKeys.insert(id)
|
||||
}
|
||||
|
||||
fileprivate func putItemCacheEntryData(id: ItemCacheEntryId, entry: Data, collectionSpec: ItemCacheCollectionSpec) {
|
||||
self.itemCacheTable.putData(id: id, entry: entry, metaTable: self.itemCacheMetaTable)
|
||||
self.currentUpdatedCacheEntryKeys.insert(id)
|
||||
}
|
||||
|
||||
func retrieveItemCacheEntry(id: ItemCacheEntryId) -> PostboxCoding? {
|
||||
return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable)
|
||||
}
|
||||
|
||||
func retrieveItemCacheEntryData(id: ItemCacheEntryId) -> Data? {
|
||||
return self.itemCacheTable.retrieveData(id: id, metaTable: self.itemCacheMetaTable)
|
||||
}
|
||||
|
||||
func clearItemCacheCollection(collectionId: ItemCacheCollectionId) {
|
||||
return self.itemCacheTable.removeAll(collectionId: collectionId)
|
||||
|
@ -160,6 +160,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, E>(
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), E> {
|
||||
return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13)], combine: { values in
|
||||
return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13)
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
|
||||
if signals.count == 0 {
|
||||
return single([T](), E.self)
|
||||
|
@ -477,6 +477,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) }
|
||||
dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) }
|
||||
dict[649453030] = { return Api.messages.MessageEditData.parse_messageEditData($0) }
|
||||
dict[1705297877] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) }
|
||||
dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) }
|
||||
dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) }
|
||||
dict[1578088377] = { return Api.messages.HistoryImportParsed.parse_historyImportParsed($0) }
|
||||
@ -736,6 +737,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
||||
dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) }
|
||||
dict[1601666510] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
|
||||
dict[-160304943] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||
dict[-1012849566] = { return Api.BaseTheme.parse_baseThemeClassic($0) }
|
||||
dict[-69724536] = { return Api.BaseTheme.parse_baseThemeDay($0) }
|
||||
dict[-1212997976] = { return Api.BaseTheme.parse_baseThemeNight($0) }
|
||||
@ -1236,6 +1238,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.MessageEditData:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.SponsoredMessages:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.LabeledPrice:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.ChatFull:
|
||||
@ -1504,6 +1508,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MessageFwdHeader:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.SponsoredMessage:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.BaseTheme:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.help.Support:
|
||||
|
@ -915,6 +915,66 @@ public struct messages {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum SponsoredMessages: TypeConstructorDescription {
|
||||
case sponsoredMessages(messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .sponsoredMessages(let messages, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(1705297877)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(messages.count))
|
||||
for item in messages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .sponsoredMessages(let messages, let chats, let users):
|
||||
return ("sponsoredMessages", [("messages", messages), ("chats", chats), ("users", users)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? {
|
||||
var _1: [Api.SponsoredMessage]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self)
|
||||
}
|
||||
var _2: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _3: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.messages.SponsoredMessages.sponsoredMessages(messages: _1!, chats: _2!, users: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChatFull: TypeConstructorDescription {
|
||||
case chatFull(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User])
|
||||
|
@ -19216,6 +19216,76 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum SponsoredMessage: TypeConstructorDescription {
|
||||
case sponsoredMessage(flags: Int32, randomId: Buffer, peerId: Api.Peer, fromId: Api.Peer, message: String, media: Api.MessageMedia?, entities: [Api.MessageEntity]?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .sponsoredMessage(let flags, let randomId, let peerId, let fromId, let message, let media, let entities):
|
||||
if boxed {
|
||||
buffer.appendInt32(-160304943)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeBytes(randomId, buffer: buffer, boxed: false)
|
||||
peerId.serialize(buffer, true)
|
||||
fromId.serialize(buffer, true)
|
||||
serializeString(message, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {media!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(entities!.count))
|
||||
for item in entities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .sponsoredMessage(let flags, let randomId, let peerId, let fromId, let message, let media, let entities):
|
||||
return ("sponsoredMessage", [("flags", flags), ("randomId", randomId), ("peerId", peerId), ("fromId", fromId), ("message", message), ("media", media), ("entities", entities)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Buffer?
|
||||
_2 = parseBytes(reader)
|
||||
var _3: Api.Peer?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
}
|
||||
var _4: Api.Peer?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
}
|
||||
var _5: String?
|
||||
_5 = parseString(reader)
|
||||
var _6: Api.MessageMedia?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.MessageMedia
|
||||
} }
|
||||
var _7: [Api.MessageEntity]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, peerId: _3!, fromId: _4!, message: _5!, media: _6, entities: _7)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum BaseTheme: TypeConstructorDescription {
|
||||
case baseThemeClassic
|
||||
|
@ -5025,6 +5025,35 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func viewSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1095836780)
|
||||
channel.serialize(buffer, true)
|
||||
serializeBytes(randomId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "channels.viewSponsoredMessage", parameters: [("channel", channel), ("randomId", randomId)]), 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 static func getSponsoredMessages(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SponsoredMessages>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-333377601)
|
||||
channel.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "channels.getSponsoredMessages", parameters: [("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.SponsoredMessages?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct payments {
|
||||
public static func getPaymentForm(flags: Int32, peer: Api.InputPeer, msgId: Int32, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
||||
@ -8155,13 +8184,14 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
public static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1070962985)
|
||||
buffer.appendInt32(-248985848)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
call.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", flags), ("call", call), ("title", title)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
if Int(flags) & Int(1 << 2) != 0 {videoPortrait!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", flags), ("call", call), ("title", title), ("videoPortrait", videoPortrait)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
|
@ -2859,14 +2859,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
}
|
||||
|
||||
public func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?) {
|
||||
public func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?) {
|
||||
if !self.stateValue.canManageCall {
|
||||
return
|
||||
}
|
||||
if (self.stateValue.recordingStartTimestamp != nil) == shouldBeRecording {
|
||||
return
|
||||
}
|
||||
self.participantsContext?.updateShouldBeRecording(shouldBeRecording, title: title)
|
||||
self.participantsContext?.updateShouldBeRecording(shouldBeRecording, title: title, videoOrientation: videoOrientation)
|
||||
}
|
||||
|
||||
private func requestCall(movingFromBroadcastToRtc: Bool) {
|
||||
|
@ -2551,7 +2551,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StopRecordingStop, action: {
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(false, title: nil)
|
||||
strongSelf.call.setShouldBeRecording(false, title: nil, videoOrientation: nil)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .forward(savedMessages: true, text: strongSelf.presentationData.strings.VoiceChat_RecordingSaved), action: { [weak self] value in
|
||||
if case .info = value, let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
@ -2581,9 +2581,9 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] in
|
||||
let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] videoOrientation in
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(true, title: "")
|
||||
strongSelf.call.setShouldBeRecording(true, title: "", videoOrientation: videoOrientation)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false })
|
||||
strongSelf.call.playTone(.recordingStarted)
|
||||
|
@ -18,13 +18,13 @@ final class VoiceChatRecordingSetupController: ViewController {
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let completion: () -> Void
|
||||
private let completion: (Bool?) -> Void
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, completion: @escaping () -> Void) {
|
||||
init(context: AccountContext, completion: @escaping (Bool?) -> Void) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
||||
@ -54,8 +54,8 @@ final class VoiceChatRecordingSetupController: ViewController {
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = VoiceChatRecordingSetupControllerNode(controller: self, context: self.context)
|
||||
self.controllerNode.completion = { [weak self] in
|
||||
self?.completion()
|
||||
self.controllerNode.completion = { [weak self] videoOrientation in
|
||||
self?.completion(videoOrientation)
|
||||
}
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
@ -143,7 +143,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
|
||||
private var mediaMode: MediaMode = .videoAndAudio
|
||||
private var videoMode: VideoMode = .portrait
|
||||
|
||||
var completion: (() -> Void)?
|
||||
var completion: ((Bool?) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
|
||||
@ -304,7 +304,19 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode,
|
||||
}
|
||||
|
||||
@objc private func donePressed() {
|
||||
self.completion?()
|
||||
let videoOrientation: Bool?
|
||||
switch self.mediaMode {
|
||||
case .audioOnly:
|
||||
videoOrientation = nil
|
||||
case .videoAndAudio:
|
||||
switch self.videoMode {
|
||||
case .portrait:
|
||||
videoOrientation = true
|
||||
case .landscape:
|
||||
videoOrientation = false
|
||||
}
|
||||
}
|
||||
self.completion?(videoOrientation)
|
||||
self.dismiss?()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
public final class AdMessageAttribute: MessageAttribute {
|
||||
public let opaqueId: Data
|
||||
|
||||
public init(opaqueId: Data) {
|
||||
self.opaqueId = opaqueId
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@ public struct Namespaces {
|
||||
public static let cachedPeerInvitationImporters: Int8 = 12
|
||||
public static let cachedPeerExportedInvitations: Int8 = 13
|
||||
public static let cachedGroupCallDisplayAsPeers: Int8 = 14
|
||||
public static let cachedAdMessageStates: Int8 = 15
|
||||
}
|
||||
|
||||
public struct UnorderedItemList {
|
||||
|
@ -1981,7 +1981,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.updateMuteState(peerId: self.myPeerId, muteState: nil, volume: nil, raiseHand: false)
|
||||
}
|
||||
|
||||
public func updateShouldBeRecording(_ shouldBeRecording: Bool, title: String?) {
|
||||
public func updateShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?) {
|
||||
var flags: Int32 = 0
|
||||
if shouldBeRecording {
|
||||
flags |= 1 << 0
|
||||
@ -1989,7 +1989,13 @@ public final class GroupCallParticipantsContext {
|
||||
if let title = title, !title.isEmpty {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), title: title))
|
||||
var videoPortrait: Api.Bool?
|
||||
if let videoOrientation = videoOrientation {
|
||||
flags |= (1 << 2)
|
||||
videoPortrait = videoOrientation ? .boolTrue : .boolFalse
|
||||
}
|
||||
|
||||
self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), title: title, videoPortrait: videoPortrait))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -0,0 +1,406 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
private class AdMessagesHistoryContextImpl {
|
||||
final class CachedMessage: Equatable, Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case opaqueId
|
||||
case text
|
||||
case textEntities
|
||||
case media
|
||||
case authorId
|
||||
}
|
||||
|
||||
public let opaqueId: Data
|
||||
public let text: String
|
||||
public let textEntities: [MessageTextEntity]
|
||||
public let media: [Media]
|
||||
public let authorId: PeerId
|
||||
|
||||
public init(
|
||||
opaqueId: Data,
|
||||
text: String,
|
||||
textEntities: [MessageTextEntity],
|
||||
media: [Media],
|
||||
authorId: PeerId
|
||||
) {
|
||||
self.opaqueId = opaqueId
|
||||
self.text = text
|
||||
self.textEntities = textEntities
|
||||
self.media = media
|
||||
self.authorId = authorId
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.opaqueId = try container.decode(Data.self, forKey: .opaqueId)
|
||||
|
||||
self.text = try container.decode(String.self, forKey: .text)
|
||||
self.textEntities = try container.decode([MessageTextEntity].self, forKey: .textEntities)
|
||||
|
||||
let mediaData = try container.decode([Data].self, forKey: .media)
|
||||
self.media = mediaData.compactMap { data -> Media? in
|
||||
return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media
|
||||
}
|
||||
|
||||
self.authorId = try container.decode(PeerId.self, forKey: .authorId)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.opaqueId, forKey: .opaqueId)
|
||||
try container.encode(self.text, forKey: .text)
|
||||
try container.encode(self.textEntities, forKey: .textEntities)
|
||||
|
||||
let mediaData = self.media.map { media -> Data in
|
||||
let encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(media)
|
||||
return encoder.makeData()
|
||||
}
|
||||
try container.encode(mediaData, forKey: .media)
|
||||
|
||||
try container.encode(self.authorId, forKey: .authorId)
|
||||
}
|
||||
|
||||
public static func ==(lhs: CachedMessage, rhs: CachedMessage) -> Bool {
|
||||
if lhs.opaqueId != rhs.opaqueId {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textEntities != rhs.textEntities {
|
||||
return false
|
||||
}
|
||||
if lhs.media.count != rhs.media.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.media.count {
|
||||
if !lhs.media[i].isEqual(to: rhs.media[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if lhs.authorId != rhs.authorId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toMessage(peerId: PeerId, transaction: Transaction) -> Message {
|
||||
var attributes: [MessageAttribute] = []
|
||||
|
||||
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId))
|
||||
if !self.textEntities.isEmpty {
|
||||
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
|
||||
attributes.append(attribute)
|
||||
}
|
||||
|
||||
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
||||
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
messagePeers[peer.id] = peer
|
||||
}
|
||||
if let peer = transaction.getPeer(self.authorId) {
|
||||
messagePeers[peer.id] = peer
|
||||
}
|
||||
|
||||
return Message(
|
||||
stableId: 0,
|
||||
stableVersion: 0,
|
||||
id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 0),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: Int32.max - 1,
|
||||
flags: [.Incoming],
|
||||
tags: [],
|
||||
globalTags: [],
|
||||
localTags: [],
|
||||
forwardInfo: nil,
|
||||
author: transaction.getPeer(self.authorId),
|
||||
text: self.text,
|
||||
attributes: attributes,
|
||||
media: self.media,
|
||||
peers: messagePeers,
|
||||
associatedMessages: SimpleDictionary<MessageId, Message>(),
|
||||
associatedMessageIds: []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
|
||||
private let maskAsSeenDisposables = DisposableDict<Data>()
|
||||
|
||||
struct CachedState: Codable, PostboxCoding {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case timestamp
|
||||
case messages
|
||||
}
|
||||
|
||||
var timestamp: Int32
|
||||
var messages: [CachedMessage]
|
||||
|
||||
init(timestamp: Int32, messages: [CachedMessage]) {
|
||||
self.timestamp = timestamp
|
||||
self.messages = messages
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.timestamp = try container.decode(Int32.self, forKey: .timestamp)
|
||||
self.messages = try container.decode([CachedMessage].self, forKey: .messages)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.timestamp, forKey: .timestamp)
|
||||
try container.encode(self.messages, forKey: .messages)
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0)
|
||||
if let messagesData = decoder.decodeOptionalDataArrayForKey("messages") {
|
||||
self.messages = messagesData.compactMap { data -> CachedMessage? in
|
||||
return try? AdaptedPostboxDecoder().decode(CachedMessage.self, from: data)
|
||||
}
|
||||
} else {
|
||||
self.messages = []
|
||||
}
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.timestamp, forKey: "timestamp")
|
||||
encoder.encodeDataArray(self.messages.compactMap { message -> Data? in
|
||||
return try? AdaptedPostboxEncoder().encode(message)
|
||||
}, forKey: "messages")
|
||||
}
|
||||
|
||||
private static let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 5, highWaterItemCount: 10)
|
||||
|
||||
public static func getCached(postbox: Postbox, peerId: PeerId) -> Signal<CachedState?, NoError> {
|
||||
return postbox.transaction { transaction -> CachedState? in
|
||||
let key = ValueBoxKey(length: 8)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
if let entry = transaction.retrieveItemCacheEntryData(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedAdMessageStates, key: key)) {
|
||||
return try? AdaptedPostboxDecoder().decode(CachedState.self, from: entry)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func setCached(transaction: Transaction, peerId: PeerId, state: CachedState?) {
|
||||
let key = ValueBoxKey(length: 8)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
let id = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedAdMessageStates, key: key)
|
||||
if let state = state, let stateData = try? AdaptedPostboxEncoder().encode(state) {
|
||||
transaction.putItemCacheEntryData(id: id, entry: stateData, collectionSpec: collectionSpec)
|
||||
} else {
|
||||
transaction.removeItemCacheEntry(id: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State: Equatable {
|
||||
var messages: [Message]
|
||||
|
||||
static func ==(lhs: State, rhs: State) -> Bool {
|
||||
if lhs.messages.count != rhs.messages.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.messages.count {
|
||||
if lhs.messages[i].id != rhs.messages[i].id {
|
||||
return false
|
||||
}
|
||||
if lhs.messages[i].stableId != rhs.messages[i].stableId {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
let state = Promise<State>()
|
||||
private var stateValue: State? {
|
||||
didSet {
|
||||
if let stateValue = self.stateValue, stateValue != oldValue {
|
||||
self.state.set(.single(stateValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
|
||||
self.stateValue = State(messages: [])
|
||||
|
||||
self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId)
|
||||
|> mapToSignal { cachedState -> Signal<State, NoError> in
|
||||
if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
|
||||
return account.postbox.transaction { transaction -> State in
|
||||
return State(messages: cachedState.messages.map { message in
|
||||
return message.toMessage(peerId: peerId, transaction: transaction)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return .single(State(messages: []))
|
||||
}
|
||||
})
|
||||
|
||||
let signal: Signal<[Message], NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapToSignal { inputChannel -> Signal<[Message], NoError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .single([])
|
||||
}
|
||||
return account.network.request(Api.functions.channels.getSponsoredMessages(channel: inputChannel))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.SponsoredMessages?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[Message], NoError> in
|
||||
guard let result = result else {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> [Message] in
|
||||
switch result {
|
||||
case let .sponsoredMessages(messages, chats, users):
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||
|
||||
var parsedMessages: [CachedMessage] = []
|
||||
|
||||
for message in messages {
|
||||
switch message {
|
||||
case let .sponsoredMessage(_, randomId, _, fromId, message, media, entities):
|
||||
var parsedEntities: [MessageTextEntity] = []
|
||||
if let entities = entities {
|
||||
parsedEntities = messageTextEntitiesFromApiEntities(entities)
|
||||
}
|
||||
|
||||
var parsedMedia: [Media] = []
|
||||
if let media = media {
|
||||
let (mediaValue, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
parsedMedia.append(mediaValue)
|
||||
}
|
||||
}
|
||||
|
||||
parsedMessages.append(CachedMessage(
|
||||
opaqueId: randomId.makeData(),
|
||||
text: message,
|
||||
textEntities: parsedEntities,
|
||||
media: parsedMedia, authorId: fromId.peerId
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), messages: parsedMessages))
|
||||
|
||||
return parsedMessages.map { message in
|
||||
return message.toMessage(peerId: peerId, transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable.set((signal
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] messages in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.stateValue = State(messages: messages)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.maskAsSeenDisposables.dispose()
|
||||
}
|
||||
|
||||
func markAsSeen(opaqueId: Data) {
|
||||
let signal: Signal<Never, NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(self.peerId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapToSignal { inputChannel -> Signal<Never, NoError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .complete()
|
||||
}
|
||||
return self.account.network.request(Api.functions.channels.viewSponsoredMessage(channel: inputChannel, randomId: Buffer(data: opaqueId)))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
self.maskAsSeenDisposables.set(signal.start(), forKey: opaqueId)
|
||||
}
|
||||
}
|
||||
|
||||
public class AdMessagesHistoryContext {
|
||||
private let queue = Queue()
|
||||
private let impl: QueueLocalObject<AdMessagesHistoryContextImpl>
|
||||
|
||||
public var state: Signal<[Message], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
let stateDisposable = impl.state.get().start(next: { state in
|
||||
subscriber.putNext(state.messages)
|
||||
})
|
||||
disposable.set(stateDisposable)
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public init(account: Account, peerId: PeerId) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return AdMessagesHistoryContextImpl(queue: queue, account: account, peerId: peerId)
|
||||
})
|
||||
}
|
||||
|
||||
public func markAsSeen(opaqueId: Data) {
|
||||
self.impl.with { impl in
|
||||
impl.markAsSeen(opaqueId: opaqueId)
|
||||
}
|
||||
}
|
||||
}
|
@ -199,5 +199,9 @@ public extension TelegramEngine {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func adMessages(peerId: PeerId) -> AdMessagesHistoryContext {
|
||||
return AdMessagesHistoryContext(account: self.account, peerId: peerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,3 +291,14 @@ public extension Message {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
var adAttribute: AdMessageAttribute? {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? AdMessageAttribute {
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,26 @@ import AccountContext
|
||||
import TelegramPresentationData
|
||||
|
||||
|
||||
func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set<MessageId>?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, pendingUnpinnedAllMessages: Bool, pendingRemovedMessages: Set<MessageId>, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia], customChannelDiscussionReadState: MessageId?, customThreadOutgoingReadState: MessageId?) -> [ChatHistoryEntry] {
|
||||
func chatHistoryEntriesForView(
|
||||
location: ChatLocation,
|
||||
view: MessageHistoryView,
|
||||
includeUnreadEntry: Bool,
|
||||
includeEmptyEntry: Bool,
|
||||
includeChatInfoEntry: Bool,
|
||||
includeSearchEntry: Bool,
|
||||
reverse: Bool,
|
||||
groupMessages: Bool,
|
||||
selectedMessages: Set<MessageId>?,
|
||||
presentationData: ChatPresentationData,
|
||||
historyAppearsCleared: Bool,
|
||||
pendingUnpinnedAllMessages: Bool,
|
||||
pendingRemovedMessages: Set<MessageId>,
|
||||
associatedData: ChatMessageItemAssociatedData,
|
||||
updatingMedia: [MessageId: ChatUpdatingMessageMedia],
|
||||
customChannelDiscussionReadState: MessageId?,
|
||||
customThreadOutgoingReadState: MessageId?,
|
||||
adMessages: [Message]
|
||||
) -> [ChatHistoryEntry] {
|
||||
if historyAppearsCleared {
|
||||
return []
|
||||
}
|
||||
@ -238,6 +257,39 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if view.laterId == nil && !view.isLoading {
|
||||
if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], !adMessages.isEmpty {
|
||||
|
||||
var nextAdMessageId: Int32 = 1
|
||||
for message in adMessages {
|
||||
let updatedMessage = Message(
|
||||
stableId: UInt32.max - 1 - UInt32(nextAdMessageId),
|
||||
stableVersion: message.stableVersion,
|
||||
id: MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: nextAdMessageId),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: lastMessage.timestamp,
|
||||
flags: message.flags,
|
||||
tags: message.tags,
|
||||
globalTags: message.globalTags,
|
||||
localTags: message.localTags,
|
||||
forwardInfo: message.forwardInfo,
|
||||
author: message.author,
|
||||
text: message.text,
|
||||
attributes: message.attributes,
|
||||
media: message.media,
|
||||
peers: message.peers,
|
||||
associatedMessages: message.associatedMessages,
|
||||
associatedMessageIds: message.associatedMessageIds
|
||||
)
|
||||
nextAdMessageId += 1
|
||||
entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if includeSearchEntry {
|
||||
if view.laterId == nil {
|
||||
if !view.entries.isEmpty {
|
||||
|
@ -469,6 +469,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
|
||||
|
||||
private let messageProcessingManager = ChatMessageThrottledProcessingManager()
|
||||
private let adSeenProcessingManager = ChatMessageThrottledProcessingManager()
|
||||
private let messageReactionsProcessingManager = ChatMessageThrottledProcessingManager()
|
||||
private let seenLiveLocationProcessingManager = ChatMessageThrottledProcessingManager()
|
||||
private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager()
|
||||
@ -556,6 +557,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private var freezeOverscrollControl: Bool = false
|
||||
private var feedback: HapticFeedback?
|
||||
var openNextChannelToRead: ((EnginePeer, TelegramEngine.NextUnreadChannelLocation) -> Void)?
|
||||
|
||||
private let adMessagesContext: AdMessagesHistoryContext?
|
||||
|
||||
private let clientId: Atomic<Int32>
|
||||
|
||||
@ -581,6 +584,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.chatPresentationDataPromise = Promise(self.currentPresentationData)
|
||||
|
||||
self.prefetchManager = InChatPrefetchManager(context: context)
|
||||
|
||||
let adMessages: Signal<[Message], NoError>
|
||||
if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject {
|
||||
let adMessagesContext = context.engine.messages.adMessages(peerId: peerId)
|
||||
self.adMessagesContext = adMessagesContext
|
||||
adMessages = adMessagesContext.state
|
||||
} else {
|
||||
self.adMessagesContext = nil
|
||||
adMessages = .single([])
|
||||
}
|
||||
|
||||
let clientId = Atomic<Int32>(value: nextClientId)
|
||||
self.clientId = clientId
|
||||
@ -606,6 +619,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.messageProcessingManager.process = { [weak context] messageIds in
|
||||
context?.account.viewTracker.updateViewCountForMessageIds(messageIds: messageIds, clientId: clientId.with { $0 })
|
||||
}
|
||||
self.adSeenProcessingManager.process = { [weak self] messageIds in
|
||||
guard let strongSelf = self, let adMessagesContext = strongSelf.adMessagesContext else {
|
||||
return
|
||||
}
|
||||
for id in messageIds {
|
||||
if let message = strongSelf.messageInCurrentHistoryView(id), let adAttribute = message.adAttribute {
|
||||
adMessagesContext.markAsSeen(opaqueId: adAttribute.opaqueId)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.messageReactionsProcessingManager.process = { [weak context] messageIds in
|
||||
context?.account.viewTracker.updateReactionsForMessageIds(messageIds: messageIds)
|
||||
}
|
||||
@ -846,8 +869,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
animatedEmojiStickers,
|
||||
customChannelDiscussionReadState,
|
||||
customThreadOutgoingReadState,
|
||||
self.currentlyPlayingMessageIdPromise.get()
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId in
|
||||
self.currentlyPlayingMessageIdPromise.get(),
|
||||
adMessages
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages in
|
||||
func applyHole() {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
@ -932,7 +956,26 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId)
|
||||
|
||||
let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, pendingRemovedMessages: pendingRemovedMessages, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState)
|
||||
let filteredEntries = chatHistoryEntriesForView(
|
||||
location: chatLocation,
|
||||
view: view,
|
||||
includeUnreadEntry: mode == .bubbles,
|
||||
includeEmptyEntry: mode == .bubbles && tagMask == nil,
|
||||
includeChatInfoEntry: mode == .bubbles,
|
||||
includeSearchEntry: includeSearchEntry && tagMask != nil,
|
||||
reverse: reverse,
|
||||
groupMessages: mode == .bubbles,
|
||||
selectedMessages: selectedMessages,
|
||||
presentationData: chatPresentationData,
|
||||
historyAppearsCleared: historyAppearsCleared,
|
||||
pendingUnpinnedAllMessages: pendingUnpinnedAllMessages,
|
||||
pendingRemovedMessages: pendingRemovedMessages,
|
||||
associatedData: associatedData,
|
||||
updatingMedia: updatingMedia,
|
||||
customChannelDiscussionReadState: customChannelDiscussionReadState,
|
||||
customThreadOutgoingReadState: customThreadOutgoingReadState,
|
||||
adMessages: adMessages
|
||||
)
|
||||
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
|
||||
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2)
|
||||
let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages))
|
||||
@ -1334,6 +1377,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
let toLaterRange = (historyView.filteredEntries.count - 1 - (visible.firstIndex - 1), historyView.filteredEntries.count - 1)
|
||||
|
||||
var messageIdsWithViewCount: [MessageId] = []
|
||||
var messageIdsWithAds: [MessageId] = []
|
||||
var messageIdsWithUpdateableReactions: [MessageId] = []
|
||||
var messageIdsWithLiveLocation: [MessageId] = []
|
||||
var messageIdsWithUnsupportedMedia: [MessageId] = []
|
||||
@ -1360,6 +1404,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
if message.id.namespace == Namespaces.Message.Cloud {
|
||||
messageIdsWithViewCount.append(message.id)
|
||||
}
|
||||
} else if attribute is AdMessageAttribute {
|
||||
messageIdsWithAds.append(message.id)
|
||||
} else if attribute is ReplyThreadMessageAttribute {
|
||||
if message.id.namespace == Namespaces.Message.Cloud {
|
||||
messageIdsWithViewCount.append(message.id)
|
||||
@ -1529,6 +1575,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
if !messageIdsWithViewCount.isEmpty {
|
||||
self.messageProcessingManager.add(messageIdsWithViewCount)
|
||||
}
|
||||
if !messageIdsWithAds.isEmpty {
|
||||
self.adSeenProcessingManager.add(messageIdsWithAds)
|
||||
}
|
||||
if !messageIdsWithUpdateableReactions.isEmpty {
|
||||
self.messageReactionsProcessingManager.add(messageIdsWithUpdateableReactions)
|
||||
}
|
||||
|
@ -288,6 +288,72 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
if messages.count == 1, let _ = messages[0].adAttribute {
|
||||
let message = messages[0]
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var actions: [ContextMenuItem] = []
|
||||
//TODO:localize
|
||||
actions.append(.action(ContextMenuActionItem(text: "What are sponsored\nmessages?", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
|
||||
}, iconSource: nil, action: { _, f in
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
actions.append(.separator)
|
||||
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
let copyTextWithEntities = {
|
||||
var messageEntities: [MessageTextEntity]?
|
||||
var restrictedText: String?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
messageEntities = attribute.entities
|
||||
}
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute {
|
||||
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
if let restrictedText = restrictedText {
|
||||
storeMessageTextInPasteboard(restrictedText, entities: nil)
|
||||
} else {
|
||||
storeMessageTextInPasteboard(message.text, entities: messageEntities)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.2, {
|
||||
let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied)
|
||||
controllerInteraction.displayUndo(content)
|
||||
})
|
||||
}
|
||||
|
||||
copyTextWithEntities()
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
if let author = message.author, let addressName = author.addressName {
|
||||
let link = "https://t.me/\(addressName)"
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
Queue.mainQueue().after(0.2, {
|
||||
controllerInteraction.displayUndo(.linkCopied(text: presentationData.strings.Conversation_LinkCopied))
|
||||
})
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
return .single(actions)
|
||||
}
|
||||
|
||||
var loadStickerSaveStatus: MediaId?
|
||||
var loadCopyMediaResource: MediaResource?
|
||||
|
@ -297,9 +297,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||
|
||||
var horizontalInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0)
|
||||
var horizontalInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)
|
||||
if displayLine {
|
||||
horizontalInsets.left += 10.0
|
||||
horizontalInsets.left += 12.0
|
||||
}
|
||||
|
||||
var preferMediaBeforeText = false
|
||||
@ -612,9 +612,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
return (initialWidth, { constrainedSize, position in
|
||||
var insets = UIEdgeInsets(top: 0.0, left: horizontalInsets.left, bottom: 5.0, right: horizontalInsets.right)
|
||||
var lineInsets = insets
|
||||
switch position {
|
||||
case .linear(.None, _):
|
||||
insets.top += 8.0
|
||||
lineInsets.top += 8.0 + 8.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -705,7 +707,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
boundingSize.width = max(boundingSize.width, videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight)
|
||||
}
|
||||
|
||||
lineHeight += insets.top + insets.bottom
|
||||
lineHeight += lineInsets.top + lineInsets.bottom
|
||||
|
||||
var imageApply: (() -> Void)?
|
||||
if let inlineImageSize = inlineImageSize, let inlineImageDimensions = inlineImageDimensions {
|
||||
@ -806,7 +808,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))?
|
||||
if let continueActionButtonLayout = continueActionButtonLayout {
|
||||
let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right)
|
||||
let (size, apply) = continueActionButtonLayout(boundingWidth - 12.0 - insets.right)
|
||||
actionButtonSizeAndApply = (size, apply)
|
||||
adjustedBoundingSize.width = max(adjustedBoundingSize.width, insets.left + size.width + insets.right)
|
||||
adjustedBoundingSize.height += 7.0 + size.height
|
||||
@ -976,7 +978,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
buttonNode.frame = CGRect(origin: CGPoint(x: 13.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size)
|
||||
buttonNode.frame = CGRect(origin: CGPoint(x: 12.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size)
|
||||
} else if let buttonNode = strongSelf.buttonNode {
|
||||
buttonNode.removeFromSupernode()
|
||||
strongSelf.buttonNode = nil
|
||||
|
@ -147,6 +147,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
if message.adAttribute != nil {
|
||||
result.removeAll()
|
||||
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
}
|
||||
|
||||
if isUnsupportedMedia {
|
||||
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
@ -177,6 +183,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id {
|
||||
hasDiscussion = false
|
||||
}
|
||||
|
||||
if firstMessage.adAttribute != nil {
|
||||
hasDiscussion = false
|
||||
}
|
||||
|
||||
if hasDiscussion {
|
||||
var canComment = false
|
||||
@ -1171,6 +1181,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
ignoreForward = true
|
||||
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt32Value(Int32(clamping: authorSignature.persistentHashValue))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags())
|
||||
displayAuthorInfo = !mergedTop.merged && incoming
|
||||
} else if let _ = item.content.firstMessage.adAttribute, let author = item.content.firstMessage.author {
|
||||
ignoreForward = true
|
||||
effectiveAuthor = author
|
||||
displayAuthorInfo = !mergedTop.merged && incoming
|
||||
} else {
|
||||
effectiveAuthor = firstMessage.author
|
||||
|
||||
@ -1287,6 +1301,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if isPreview {
|
||||
needShareButton = false
|
||||
}
|
||||
if item.content.firstMessage.adAttribute != nil {
|
||||
needShareButton = false
|
||||
}
|
||||
|
||||
var tmpWidth: CGFloat
|
||||
if allowFullWidth {
|
||||
@ -1549,7 +1566,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
if initialDisplayHeader && displayAuthorInfo {
|
||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil {
|
||||
authorNameString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
|
||||
authorNameColor = chatMessagePeerIdColors[Int(peer.id.id._internalGetInt32Value() % 7)]
|
||||
} else if let effectiveAuthor = effectiveAuthor {
|
||||
|
@ -16,6 +16,9 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
private let selectAll: Bool
|
||||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||
if self.message.adAttribute != nil {
|
||||
return .single(false)
|
||||
}
|
||||
let viewKey = PostboxViewKey.messages(Set([self.message.id]))
|
||||
return self.postbox.combinedView(keys: [viewKey])
|
||||
|> map { views -> Bool in
|
||||
|
@ -63,17 +63,21 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
self.contentNode.activateAction = { [weak self] in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
var webPageContent: TelegramMediaWebpageLoadedContent?
|
||||
for media in item.message.media {
|
||||
if let media = media as? TelegramMediaWebpage {
|
||||
if case let .Loaded(content) = media.content {
|
||||
webPageContent = content
|
||||
if let _ = item.message.adAttribute, let author = item.message.author {
|
||||
item.controllerInteraction.openPeer(author.id, .chat(textInputState: nil, subject: nil, peekData: nil), nil)
|
||||
} else {
|
||||
var webPageContent: TelegramMediaWebpageLoadedContent?
|
||||
for media in item.message.media {
|
||||
if let media = media as? TelegramMediaWebpage {
|
||||
if case let .Loaded(content) = media.content {
|
||||
webPageContent = content
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if let webpage = webPageContent {
|
||||
item.controllerInteraction.openUrl(webpage.url, false, nil, nil)
|
||||
if let webpage = webPageContent {
|
||||
item.controllerInteraction.openUrl(webpage.url, false, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,6 +112,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
var actionIcon: ChatMessageAttachedContentActionIcon?
|
||||
var actionTitle: String?
|
||||
|
||||
var displayLine: Bool = true
|
||||
|
||||
if let webpage = webPageContent {
|
||||
let type = websiteType(of: webpage.websiteName)
|
||||
@ -297,9 +303,33 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let _ = item.message.adAttribute {
|
||||
title = nil
|
||||
subtitle = nil
|
||||
text = item.message.text
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute.entities
|
||||
}
|
||||
}
|
||||
for media in item.message.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage, _ as TelegramMediaFile:
|
||||
mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags())
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let author = item.message.author as? TelegramChannel, case .group = author.info {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewGroup
|
||||
} else {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewChannel
|
||||
}
|
||||
displayLine = false
|
||||
}
|
||||
|
||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, preparePosition, constrainedSize)
|
||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize)
|
||||
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
||||
@ -345,9 +375,17 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
guard let item = self.item else {
|
||||
return .none
|
||||
}
|
||||
if self.bounds.contains(point) {
|
||||
let contentNodeFrame = self.contentNode.frame
|
||||
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating)
|
||||
|
||||
if item.message.adAttribute != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .none:
|
||||
break
|
||||
|
@ -29,6 +29,11 @@ private func dateStringForDay(strings: PresentationStrings, dateTimeFormat: Pres
|
||||
}
|
||||
|
||||
func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, reactionCount: Int) -> String {
|
||||
if message.adAttribute != nil {
|
||||
//TODO:localize
|
||||
return "sponsored"
|
||||
}
|
||||
|
||||
let timestamp: Int32
|
||||
if let scheduleTime = message.scheduleTime {
|
||||
timestamp = scheduleTime
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 6b986a40a333502288db5bcb9e1af7dfa042dcc9
|
||||
Subproject commit 0704bb6d9e32f5d74bb2994b1dcfe49698c6c443
|
Loading…
x
Reference in New Issue
Block a user