Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-11-07 17:34:39 +04:00
commit ec4283159b
35 changed files with 701 additions and 168 deletions

View File

@ -8242,5 +8242,8 @@ Sorry for the inconvenience.";
"Undo.DeletedTopic" = "Topic Deleted"; "Undo.DeletedTopic" = "Topic Deleted";
"ChatList.MaxThreadPinsFinalText_1" = "Sorry, you can't pin more than **%@** thread to the top. Unpin some that are currently pinned.";
"ChatList.MaxThreadPinsFinalText_any" = "Sorry, you can't pin more than **%@** threads to the top. Unpin some that are currently pinned.";
"EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons"; "EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons";
"EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found"; "EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found";

View File

@ -519,7 +519,19 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default) f(.default)
let _ = context.engine.peers.setForumChannelTopicPinned(id: peerId, threadId: threadId, isPinned: !isPinned).start() let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId)
|> deliverOnMainQueue).start(error: { error in
switch error {
case let .limitReached(count):
if let chatListController = chatListController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text = presentationData.strings.ChatList_MaxThreadPinsFinalText(Int32(count))
chatListController.present(textAlertController(context: context, title: presentationData.strings.Premium_LimitReached, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true), in: .window(.root))
}
default:
break
}
})
}))) })))
} }

View File

@ -3874,7 +3874,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
private func setPeerThreadPinned(peerId: EnginePeer.Id, threadId: Int64, isPinned: Bool) { private func setPeerThreadPinned(peerId: EnginePeer.Id, threadId: Int64, isPinned: Bool) {
self.actionDisposables.add(self.context.engine.peers.setForumChannelTopicPinned(id: peerId, threadId: threadId, isPinned: isPinned).start()) self.actionDisposables.add(self.context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId).start())
} }
public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) { public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) {

View File

@ -795,7 +795,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage
} }
let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author {
result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)"
} }
@ -829,7 +829,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage
} }
let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author {
result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)"
} }
@ -1387,7 +1387,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var hideAuthor = false var hideAuthor = false
switch contentPeer { switch contentPeer {
case let .chat(itemPeer): case let .chat(itemPeer):
var (peer, initialHideAuthor, messageText, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) var (peer, initialHideAuthor, messageText, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText { if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText {
initialHideAuthor = true initialHideAuthor = true

View File

@ -45,11 +45,18 @@ private func messageGroupType(messages: [EngineMessage]) -> MessageGroupType {
return currentType return currentType
} }
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) { public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, contentSettings: ContentSettings, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) {
let peer: EnginePeer? let peer: EnginePeer?
let message = messages.last let message = messages.last
if let restrictionReason = message?._asMessage().restrictionReason(platform: "ios", contentSettings: contentSettings) {
return (nil, false, restrictionReason, nil, nil)
}
if let restrictionReason = chatPeer.chatMainPeer?.restrictionText(platform: "ios", contentSettings: contentSettings) {
return (nil, false, restrictionReason, nil, nil)
}
var hideAuthor = false var hideAuthor = false
var messageText: String var messageText: String
var spoilers: [NSRange]? var spoilers: [NSRange]?

View File

@ -1697,85 +1697,160 @@ public final class ChatListNode: ListView {
}) })
self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState -> Signal<Bool, NoError> in self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState -> Signal<Bool, NoError> in
if let strongSelf = self, let filteredEntries = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView.filteredEntries { guard let strongSelf = self, let filteredEntries = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView.filteredEntries else {
guard case let .chatList(groupId) = strongSelf.location else { return .single(false)
return .single(false) }
guard fromIndex >= 0 && fromIndex < filteredEntries.count && toIndex >= 0 && toIndex < filteredEntries.count else {
return .single(false)
}
switch strongSelf.location {
case let .chatList(groupId):
let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex]
let toEntry = filteredEntries[filteredEntries.count - 1 - toIndex]
var referenceId: EngineChatList.PinnedItem.Id?
var beforeAll = false
switch toEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
if promoInfo != nil {
beforeAll = true
} else {
if case let .chatList(chatListIndex) = index {
referenceId = .peer(chatListIndex.messageIndex.id.peerId)
}
}
default:
break
} }
if fromIndex >= 0 && fromIndex < filteredEntries.count && toIndex >= 0 && toIndex < filteredEntries.count { if case let .index(index) = fromEntry.sortIndex, case let .chatList(chatListIndex) = index, let _ = chatListIndex.pinningIndex {
let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex] let location: TogglePeerChatPinnedLocation
let toEntry = filteredEntries[filteredEntries.count - 1 - toIndex] if let chatListFilter = chatListFilter {
location = .filter(chatListFilter.id)
} else {
location = .group(groupId._asGroup())
}
var referenceId: EngineChatList.PinnedItem.Id? let engine = strongSelf.context.engine
var beforeAll = false return engine.peers.getPinnedItemIds(location: location)
switch toEntry { |> mapToSignal { itemIds -> Signal<Bool, NoError> in
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _): var itemIds = itemIds
if promoInfo != nil {
beforeAll = true var itemId: EngineChatList.PinnedItem.Id?
} else { switch fromEntry {
if case let .chatList(chatListIndex) = index { case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
referenceId = .peer(chatListIndex.messageIndex.id.peerId) if case let .chatList(index) = index {
itemId = .peer(index.messageIndex.id.peerId)
} }
}
default: default:
break break
}
if case let .index(index) = fromEntry.sortIndex, case let .chatList(chatListIndex) = index, let _ = chatListIndex.pinningIndex {
let location: TogglePeerChatPinnedLocation
if let chatListFilter = chatListFilter {
location = .filter(chatListFilter.id)
} else {
location = .group(groupId._asGroup())
} }
let engine = strongSelf.context.engine if let itemId = itemId {
return engine.peers.getPinnedItemIds(location: location) itemIds = itemIds.filter({ $0 != itemId })
|> mapToSignal { itemIds -> Signal<Bool, NoError> in if let referenceId = referenceId {
var itemIds = itemIds var inserted = false
for i in 0 ..< itemIds.count {
var itemId: EngineChatList.PinnedItem.Id? if itemIds[i] == referenceId {
switch fromEntry { if fromIndex < toIndex {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): itemIds.insert(itemId, at: i + 1)
if case let .chatList(index) = index { } else {
itemId = .peer(index.messageIndex.id.peerId) itemIds.insert(itemId, at: i)
}
default:
break
}
if let itemId = itemId {
itemIds = itemIds.filter({ $0 != itemId })
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< itemIds.count {
if itemIds[i] == referenceId {
if fromIndex < toIndex {
itemIds.insert(itemId, at: i + 1)
} else {
itemIds.insert(itemId, at: i)
}
inserted = true
break
} }
inserted = true
break
} }
if !inserted { }
itemIds.append(itemId) if !inserted {
}
} else if beforeAll {
itemIds.insert(itemId, at: 0)
} else {
itemIds.append(itemId) itemIds.append(itemId)
} }
return engine.peers.reorderPinnedItemIds(location: location, itemIds: itemIds) } else if beforeAll {
itemIds.insert(itemId, at: 0)
} else { } else {
return .single(false) itemIds.append(itemId)
} }
return engine.peers.reorderPinnedItemIds(location: location, itemIds: itemIds)
} else {
return .single(false)
} }
} }
} else {
return .single(false)
}
case let .forum(peerId):
let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex]
let toEntry = filteredEntries[filteredEntries.count - 1 - toIndex]
var referenceId: Int64?
var beforeAll = false
switch toEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
if promoInfo != nil {
beforeAll = true
} else {
if case let .forum(_, _, threadId, _, _) = index {
referenceId = threadId
}
}
default:
break
}
if case let .index(index) = fromEntry.sortIndex, case let .forum(pinningIndex, _, _, _, _) = index, case .index = pinningIndex {
let engine = strongSelf.context.engine
return engine.peers.getForumChannelPinnedTopics(id: peerId)
|> mapToSignal { itemIds -> Signal<Bool, NoError> in
var itemIds = itemIds
var itemId: Int64?
switch fromEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if case let .forum(_, _, threadId, _, _) = index {
itemId = threadId
}
default:
break
}
if let itemId = itemId {
itemIds = itemIds.filter({ $0 != itemId })
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< itemIds.count {
if itemIds[i] == referenceId {
if fromIndex < toIndex {
itemIds.insert(itemId, at: i + 1)
} else {
itemIds.insert(itemId, at: i)
}
inserted = true
break
}
}
if !inserted {
itemIds.append(itemId)
}
} else if beforeAll {
itemIds.insert(itemId, at: 0)
} else {
itemIds.append(itemId)
}
return engine.peers.setForumChannelPinnedTopics(id: peerId, threadIds: itemIds)
|> map { _ -> Bool in
}
|> `catch` { _ -> Signal<Bool, NoError> in
return .single(false)
}
|> then(Signal<Bool, NoError>.single(true))
} else {
return .single(false)
}
}
} else {
return .single(false)
} }
} }
return .single(false)
} }
var startedScrollingAtUpperBound = false var startedScrollingAtUpperBound = false

View File

@ -4,10 +4,16 @@ import Postbox
public extension Message { public extension Message {
func isRestricted(platform: String, contentSettings: ContentSettings) -> Bool { func isRestricted(platform: String, contentSettings: ContentSettings) -> Bool {
return self.restrictionReason(platform: platform, contentSettings: contentSettings) != nil
}
func restrictionReason(platform: String, contentSettings: ContentSettings) -> String? {
if let attribute = self.restrictedContentAttribute { if let attribute = self.restrictedContentAttribute {
return attribute.platformText(platform: platform, contentSettings: contentSettings) != nil if let value = attribute.platformText(platform: platform, contentSettings: contentSettings) {
return value
}
} }
return false return nil
} }
} }

View File

@ -268,7 +268,7 @@ private final class ChatListViewSpaceState {
} }
postboxLog("allIndices not unique, repeated: \(debugRepeatedIndices)") postboxLog("allIndices not unique, repeated: \(debugRepeatedIndices)")
assert(false) //assert(false)
//preconditionFailure() //preconditionFailure()
} }
if Set(allEntityIds).count != allEntityIds.count { if Set(allEntityIds).count != allEntityIds.count {

View File

@ -778,7 +778,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) }
dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) } dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) }
dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) } dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) }
dict[-158027602] = { return Api.Update.parse_updateChannelPinnedTopic($0) } dict[422509539] = { return Api.Update.parse_updateChannelPinnedTopic($0) }
dict[-31881726] = { return Api.Update.parse_updateChannelPinnedTopics($0) }
dict[-366410403] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } dict[-366410403] = { return Api.Update.parse_updateChannelReadMessagesContents($0) }
dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) } dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) }
dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) }

View File

@ -671,7 +671,8 @@ public extension Api {
case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32)
case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32)
case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32)
case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32?) case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32)
case updateChannelPinnedTopics(flags: Int32, channelId: Int64, order: [Int32]?)
case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32]) case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32])
case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?)
case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction)
@ -927,11 +928,23 @@ public extension Api {
break break
case .updateChannelPinnedTopic(let flags, let channelId, let topicId): case .updateChannelPinnedTopic(let flags, let channelId, let topicId):
if boxed { if boxed {
buffer.appendInt32(-158027602) buffer.appendInt32(422509539)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false) serializeInt64(channelId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topicId!, buffer: buffer, boxed: false)} serializeInt32(topicId, buffer: buffer, boxed: false)
break
case .updateChannelPinnedTopics(let flags, let channelId, let order):
if boxed {
buffer.appendInt32(-31881726)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(channelId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(order!.count))
for item in order! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
break break
case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages):
if boxed { if boxed {
@ -1741,6 +1754,8 @@ public extension Api {
return ("updateChannelParticipant", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("date", String(describing: date)), ("actorId", String(describing: actorId)), ("userId", String(describing: userId)), ("prevParticipant", String(describing: prevParticipant)), ("newParticipant", String(describing: newParticipant)), ("invite", String(describing: invite)), ("qts", String(describing: qts))]) return ("updateChannelParticipant", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("date", String(describing: date)), ("actorId", String(describing: actorId)), ("userId", String(describing: userId)), ("prevParticipant", String(describing: prevParticipant)), ("newParticipant", String(describing: newParticipant)), ("invite", String(describing: invite)), ("qts", String(describing: qts))])
case .updateChannelPinnedTopic(let flags, let channelId, let topicId): case .updateChannelPinnedTopic(let flags, let channelId, let topicId):
return ("updateChannelPinnedTopic", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topicId", String(describing: topicId))]) return ("updateChannelPinnedTopic", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topicId", String(describing: topicId))])
case .updateChannelPinnedTopics(let flags, let channelId, let order):
return ("updateChannelPinnedTopics", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("order", String(describing: order))])
case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages):
return ("updateChannelReadMessagesContents", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topMsgId", String(describing: topMsgId)), ("messages", String(describing: messages))]) return ("updateChannelReadMessagesContents", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topMsgId", String(describing: topMsgId)), ("messages", String(describing: messages))])
case .updateChannelTooLong(let flags, let channelId, let pts): case .updateChannelTooLong(let flags, let channelId, let pts):
@ -2307,12 +2322,31 @@ public extension Api {
var _2: Int64? var _2: Int64?
_2 = reader.readInt64() _2 = reader.readInt64()
var _3: Int32? var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } _3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3!)
}
else {
return nil
}
}
public static func parse_updateChannelPinnedTopics(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 { if _c1 && _c2 && _c3 {
return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3) return Api.Update.updateChannelPinnedTopics(flags: _1!, channelId: _2!, order: _3)
} }
else { else {
return nil return nil

View File

@ -2443,6 +2443,27 @@ public extension Api.functions.channels {
}) })
} }
} }
public extension Api.functions.channels {
static func reorderPinnedForumTopics(flags: Int32, channel: Api.InputChannel, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(693150095)
serializeInt32(flags, buffer: buffer, boxed: false)
channel.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(order.count))
for item in order {
serializeInt32(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "channels.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("order", String(describing: order))]), 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.channels { public extension Api.functions.channels {
static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -179,6 +179,8 @@ public class ManagedAudioSessionControl {
} }
public final class ManagedAudioSession { public final class ManagedAudioSession {
public private(set) static var shared: ManagedAudioSession?
private var nextId: Int32 = 0 private var nextId: Int32 = 0
private let queue: Queue private let queue: Queue
private let hasLoudspeaker: Bool private let hasLoudspeaker: Bool
@ -256,6 +258,8 @@ public final class ManagedAudioSession {
self.isHeadsetPluggedInValue = self.isHeadsetPluggedIn() self.isHeadsetPluggedInValue = self.isHeadsetPluggedIn()
self.updateCurrentAudioRouteInfo() self.updateCurrentAudioRouteInfo()
} }
ManagedAudioSession.shared = self
} }
deinit { deinit {
@ -784,6 +788,61 @@ public final class ManagedAudioSession {
} }
} }
public func applyVoiceChatOutputModeInCurrentAudioSession(outputMode: AudioSessionOutputMode) {
managedAudioSessionLog("applyVoiceChatOutputModeInCurrentAudioSession \(outputMode)")
do {
var resetToBuiltin = false
switch outputMode {
case .system:
resetToBuiltin = true
case let .custom(output):
switch output {
case .builtin:
resetToBuiltin = true
case .speaker:
if let routes = AVAudioSession.sharedInstance().availableInputs {
for route in routes {
if route.portType == .builtInMic {
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
break
}
}
}
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
case .headphones:
break
case let .port(port):
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
if let routes = AVAudioSession.sharedInstance().availableInputs {
for route in routes {
if route.uid == port.uid {
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
break
}
}
}
}
case .speakerIfNoHeadphones:
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
}
if resetToBuiltin {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
if let routes = AVAudioSession.sharedInstance().availableInputs {
for route in routes {
if route.portType == .builtInMic {
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
break
}
}
}
}
} catch let e {
managedAudioSessionLog("applyVoiceChatOutputModeInCurrentAudioSession error: \(e)")
}
}
private func setupOutputMode(_ outputMode: AudioSessionOutputMode, type: ManagedAudioSessionType) throws { private func setupOutputMode(_ outputMode: AudioSessionOutputMode, type: ManagedAudioSessionType) throws {
managedAudioSessionLog("ManagedAudioSession setup \(outputMode) for \(type)") managedAudioSessionLog("ManagedAudioSession setup \(outputMode) for \(type)")
var resetToBuiltin = false var resetToBuiltin = false

View File

@ -8,6 +8,7 @@ import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import AppBundle import AppBundle
import AccountContext import AccountContext
import TelegramAudio
private let sharedProviderDelegate: AnyObject? = { private let sharedProviderDelegate: AnyObject? = {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
@ -107,6 +108,10 @@ public final class CallKitIntegration {
} }
} }
} }
public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
(sharedProviderDelegate as? CallKitProviderDelegate)?.applyVoiceChatOutputMode(outputMode: outputMode)
}
} }
@available(iOSApplicationExtension 10.0, iOS 10.0, *) @available(iOSApplicationExtension 10.0, iOS 10.0, *)
@ -125,6 +130,9 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
private var setCallMuted: ((UUID, Bool) -> Void)? private var setCallMuted: ((UUID, Bool) -> Void)?
private var audioSessionActivationChanged: ((Bool) -> Void)? private var audioSessionActivationChanged: ((Bool) -> Void)?
private var isAudioSessionActive: Bool = false
private var pendingVoiceChatOutputMode: AudioSessionOutputMode?
private let disposableSet = DisposableSet() private let disposableSet = DisposableSet()
fileprivate var audioSessionActivePromise: ValuePromise<Bool>? fileprivate var audioSessionActivePromise: ValuePromise<Bool>?
@ -163,7 +171,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
private func requestTransaction(_ transaction: CXTransaction, completion: ((Bool) -> Void)? = nil) { private func requestTransaction(_ transaction: CXTransaction, completion: ((Bool) -> Void)? = nil) {
self.callController.request(transaction) { error in self.callController.request(transaction) { error in
if let error = error { if let error = error {
print("Error requesting transaction: \(error)") print("Error requesting transaction \(transaction): \(error)")
} }
completion?(error == nil) completion?(error == nil)
} }
@ -238,6 +246,12 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
update.supportsDTMF = false update.supportsDTMF = false
update.hasVideo = isVideo update.hasVideo = isVideo
do {
try AVAudioSession.sharedInstance().setMode(.default)
} catch let e {
print("AVAudioSession.sharedInstance().setMode(.default) error \(e)")
}
self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
completion?(error as NSError?) completion?(error as NSError?)
}) })
@ -321,13 +335,28 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate {
} }
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("provider didActivate default? \(audioSession === AVAudioSession.sharedInstance())")
self.isAudioSessionActive = true
self.audioSessionActivationChanged?(true) self.audioSessionActivationChanged?(true)
self.audioSessionActivePromise?.set(true) self.audioSessionActivePromise?.set(true)
if let outputMode = self.pendingVoiceChatOutputMode {
self.pendingVoiceChatOutputMode = nil
ManagedAudioSession.shared?.applyVoiceChatOutputModeInCurrentAudioSession(outputMode: outputMode)
}
} }
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
self.isAudioSessionActive = false
self.audioSessionActivationChanged?(false) self.audioSessionActivationChanged?(false)
self.audioSessionActivePromise?.set(false) self.audioSessionActivePromise?.set(false)
} }
func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
if self.isAudioSessionActive {
ManagedAudioSession.shared?.applyVoiceChatOutputModeInCurrentAudioSession(outputMode: outputMode)
} else {
self.pendingVoiceChatOutputMode = outputMode
}
}
} }

View File

@ -413,6 +413,9 @@ public final class PresentationCallImpl: PresentationCall {
return return
} }
strongSelf.audioOutputStateValue = (availableOutputs, currentOutput) strongSelf.audioOutputStateValue = (availableOutputs, currentOutput)
if let currentOutput = currentOutput {
strongSelf.currentAudioOutputValue = currentOutput
}
var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput)) var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput))
if !didReceiveAudioOutputs { if !didReceiveAudioOutputs {
@ -437,7 +440,7 @@ public final class PresentationCallImpl: PresentationCall {
let audioSessionActive: Signal<Bool, NoError> let audioSessionActive: Signal<Bool, NoError>
if let callKitIntegration = strongSelf.callKitIntegration { if let callKitIntegration = strongSelf.callKitIntegration {
audioSessionActive = callKitIntegration.audioSessionActive audioSessionActive = callKitIntegration.audioSessionActive
|> filter { $0 } /*|> filter { $0 }
|> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in |> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in
if let strongSelf = self, let _ = strongSelf.audioSessionControl { if let strongSelf = self, let _ = strongSelf.audioSessionControl {
//audioSessionControl.activate({ _ in }) //audioSessionControl.activate({ _ in })
@ -445,7 +448,7 @@ public final class PresentationCallImpl: PresentationCall {
subscriber.putNext(true) subscriber.putNext(true)
subscriber.putCompletion() subscriber.putCompletion()
return EmptyDisposable return EmptyDisposable
}) })*/
} else { } else {
audioSessionControl.activate({ _ in }) audioSessionControl.activate({ _ in })
audioSessionActive = .single(true) audioSessionActive = .single(true)
@ -534,8 +537,12 @@ public final class PresentationCallImpl: PresentationCall {
} }
if let audioSessionControl = audioSessionControl, previous == nil || previousControl == nil { if let audioSessionControl = audioSessionControl, previous == nil || previousControl == nil {
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue)) if let callKitIntegration = self.callKitIntegration {
audioSessionControl.setup(synchronous: true) callKitIntegration.applyVoiceChatOutputMode(outputMode: .custom(self.currentAudioOutputValue))
} else {
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
audioSessionControl.setup(synchronous: true)
}
} }
let mappedVideoState: PresentationCallState.VideoState let mappedVideoState: PresentationCallState.VideoState
@ -1031,7 +1038,11 @@ public final class PresentationCallImpl: PresentationCall {
)) ))
if let audioSessionControl = self.audioSessionControl { if let audioSessionControl = self.audioSessionControl {
audioSessionControl.setOutputMode(.custom(output)) if let callKitIntegration = self.callKitIntegration {
callKitIntegration.applyVoiceChatOutputMode(outputMode: .custom(self.currentAudioOutputValue))
} else {
audioSessionControl.setOutputMode(.custom(output))
}
} }
} }

View File

@ -1648,7 +1648,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
outgoingAudioBitrateKbit = Int32(value) outgoingAudioBitrateKbit = Int32(value)
} }
genericCallContext = .call(OngoingGroupCallContext(video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: self.audioSessionActive.get(), video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in
let disposable = MetaDisposable() let disposable = MetaDisposable()
Queue.mainQueue().async { Queue.mainQueue().async {
guard let strongSelf = self else { guard let strongSelf = self else {
@ -2966,7 +2966,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.hasScreencast = true self.hasScreencast = true
let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false, logPath: "") let screencastCallContext = OngoingGroupCallContext(audioSessionActive: .single(true), video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false, logPath: "")
self.screencastCallContext = screencastCallContext self.screencastCallContext = screencastCallContext
self.screencastJoinDisposable.set((screencastCallContext.joinPayload self.screencastJoinDisposable.set((screencastCallContext.joinPayload

View File

@ -93,7 +93,8 @@ enum AccountStateMutationOperation {
case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32)
case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?)
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation) case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
case UpdatePinnedTopic(peerId: PeerId, threadId: Int64?) case UpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool)
case UpdatePinnedTopicOrder(peerId: PeerId, threadIds: [Int64])
case ReadMessageContents((PeerId?, [Int32])) case ReadMessageContents((PeerId?, [Int32]))
case UpdateMessageImpressionCount(MessageId, Int32) case UpdateMessageImpressionCount(MessageId, Int32)
case UpdateMessageForwardsCount(MessageId, Int32) case UpdateMessageForwardsCount(MessageId, Int32)
@ -475,8 +476,12 @@ struct AccountMutableState {
self.addOperation(.UpdatePinnedItemIds(groupId, operation)) self.addOperation(.UpdatePinnedItemIds(groupId, operation))
} }
mutating func addUpdatePinnedTopic(peerId: PeerId, threadId: Int64?) { mutating func addUpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) {
self.addOperation(.UpdatePinnedTopic(peerId: peerId, threadId: threadId)) self.addOperation(.UpdatePinnedTopic(peerId: peerId, threadId: threadId, isPinned: isPinned))
}
mutating func addUpdatePinnedTopicOrder(peerId: PeerId, threadIds: [Int64]) {
self.addOperation(.UpdatePinnedTopicOrder(peerId: peerId, threadIds: threadIds))
} }
mutating func addReadMessagesContents(_ peerIdsAndMessageIds: (PeerId?, [Int32])) { mutating func addReadMessagesContents(_ peerIdsAndMessageIds: (PeerId?, [Int32])) {
@ -545,7 +550,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {

View File

@ -367,21 +367,16 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
public enum SetForumChannelTopicPinnedError { public enum SetForumChannelTopicPinnedError {
case generic case generic
case limitReached(Int)
} }
func _internal_setForumChannelTopicPinned(account: Account, id: EnginePeer.Id, threadId: Int64, isPinned: Bool) -> Signal<Never, SetForumChannelTopicPinnedError> { func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, SetForumChannelTopicPinnedError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in return account.postbox.transaction { transaction -> Api.InputChannel? in
guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else { guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else {
return nil return nil
} }
if isPinned { transaction.setPeerPinnedThreads(peerId: id, threadIds: threadIds)
transaction.setPeerPinnedThreads(peerId: id, threadIds: [threadId])
} else {
if transaction.getPeerPinnedThreads(peerId: id).contains(threadId) {
transaction.setPeerPinnedThreads(peerId: id, threadIds: [])
}
}
return inputChannel return inputChannel
} }
@ -390,13 +385,11 @@ func _internal_setForumChannelTopicPinned(account: Account, id: EnginePeer.Id, t
guard let inputChannel = inputChannel else { guard let inputChannel = inputChannel else {
return .fail(.generic) return .fail(.generic)
} }
var flags: Int32 = 0
flags |= (1 << 2)
return account.network.request(Api.functions.channels.updatePinnedForumTopic( return account.network.request(Api.functions.channels.reorderPinnedForumTopics(
flags: 1 << 0,
channel: inputChannel, channel: inputChannel,
topicId: Int32(clamping: threadId), order: threadIds.map(Int32.init(clamping:))
pinned: isPinned ? .boolTrue : .boolFalse
)) ))
|> mapError { _ -> SetForumChannelTopicPinnedError in |> mapError { _ -> SetForumChannelTopicPinnedError in
return .generic return .generic

View File

@ -114,6 +114,11 @@ private func peerIdsRequiringLocalChatStateFromUpdates(_ updates: [Api.Update])
case let .updateChannelTooLong(_, channelId, _): case let .updateChannelTooLong(_, channelId, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId) peerIds.insert(peerId)
case let .updateChannelPinnedTopics(_, channelId, order):
if order == nil {
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId)
}
case let .updateFolderPeers(folderPeers, _, _): case let .updateFolderPeers(folderPeers, _, _):
for peer in folderPeers { for peer in folderPeers {
switch peer { switch peer {
@ -337,11 +342,16 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda
peerIds.insert(messageId.peerId) peerIds.insert(messageId.peerId)
} }
switch update { switch update {
case let .updateChannelTooLong(_, channelId, _): case let .updateChannelTooLong(_, channelId, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId)
case let .updateChannelPinnedTopics(_, channelId, order):
if order == nil {
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId) peerIds.insert(peerId)
default: }
break default:
break
} }
} }
case .differenceEmpty: case .differenceEmpty:
@ -359,11 +369,16 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda
peerIds.insert(messageId.peerId) peerIds.insert(messageId.peerId)
} }
switch update { switch update {
case let .updateChannelTooLong(_, channelId, _): case let .updateChannelTooLong(_, channelId, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId)
case let .updateChannelPinnedTopics(_, channelId, order):
if order == nil {
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
peerIds.insert(peerId) peerIds.insert(peerId)
default: }
break default:
break
} }
} }
case .differenceTooLong: case .differenceTooLong:
@ -725,6 +740,20 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
} else { } else {
updatesByChannel[peerId]!.append(update) updatesByChannel[peerId]!.append(update)
} }
case let .updateChannelPinnedTopic(_, channelId, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
if updatesByChannel[peerId] == nil {
updatesByChannel[peerId] = [update]
} else {
updatesByChannel[peerId]!.append(update)
}
case let .updateChannelPinnedTopics(_, channelId, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
if updatesByChannel[peerId] == nil {
updatesByChannel[peerId] = [update]
} else {
updatesByChannel[peerId]!.append(update)
}
case let .updateDeleteChannelMessages(channelId, _, _, _): case let .updateDeleteChannelMessages(channelId, _, _, _):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
if updatesByChannel[peerId] == nil { if updatesByChannel[peerId] == nil {
@ -860,6 +889,15 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
channelsToPoll.insert(peerId) channelsToPoll.insert(peerId)
} }
} }
case let .updateChannelPinnedTopics(_, channelId, order):
if let order = order {
updatedState.addUpdatePinnedTopicOrder(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadIds: order.map(Int64.init))
} else {
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
if !channelsToPoll.contains(peerId) {
channelsToPoll.insert(peerId)
}
}
case let .updateDeleteChannelMessages(channelId, messages, pts: pts, ptsCount): case let .updateDeleteChannelMessages(channelId, messages, pts: pts, ptsCount):
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
if let previousState = updatedState.channelStates[peerId] { if let previousState = updatedState.channelStates[peerId] {
@ -1375,8 +1413,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
} else { } else {
updatedState.addUpdatePinnedItemIds(groupId: groupId, operation: .sync) updatedState.addUpdatePinnedItemIds(groupId: groupId, operation: .sync)
} }
case let .updateChannelPinnedTopic(_, channelId, topicId): case let .updateChannelPinnedTopic(flags, channelId, topicId):
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: topicId.flatMap(Int64.init)) let isPinned = (flags & (1 << 0)) != 0
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
case let .updateReadMessagesContents(messages, _, _): case let .updateReadMessagesContents(messages, _, _):
updatedState.addReadMessagesContents((nil, messages)) updatedState.addReadMessagesContents((nil, messages))
case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages): case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages):
@ -2847,7 +2886,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState? var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -3757,12 +3796,18 @@ func replayFinalState(
case .sync: case .sync:
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId) addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
} }
case let .UpdatePinnedTopic(peerId, threadId): case let .UpdatePinnedTopic(peerId, threadId, isPinned):
if let threadId = threadId { var currentThreadIds = transaction.getPeerPinnedThreads(peerId: peerId)
transaction.setPeerPinnedThreads(peerId: peerId, threadIds: [threadId]) if isPinned {
if !currentThreadIds.contains(threadId) {
currentThreadIds.insert(threadId, at: 0)
}
} else { } else {
transaction.setPeerPinnedThreads(peerId: peerId, threadIds: []) currentThreadIds.removeAll(where: { $0 == threadId })
} }
transaction.setPeerPinnedThreads(peerId: peerId, threadIds: currentThreadIds)
case let .UpdatePinnedTopicOrder(peerId, threadIds):
transaction.setPeerPinnedThreads(peerId: peerId, threadIds: threadIds)
case let .ReadMessageContents(peerIdAndMessageIds): case let .ReadMessageContents(peerIdAndMessageIds):
let (peerId, messageIds) = peerIdAndMessageIds let (peerId, messageIds) = peerIdAndMessageIds

View File

@ -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 148 return 149
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -858,8 +858,34 @@ public extension TelegramEngine {
|> ignoreValues |> ignoreValues
} }
public func setForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64, isPinned: Bool) -> Signal<Never, SetForumChannelTopicPinnedError> { public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
return _internal_setForumChannelTopicPinned(account: self.account, id: id, threadId: threadId, isPinned: isPinned) return self.account.postbox.transaction { transaction -> [Int64] in
return transaction.getPeerPinnedThreads(peerId: id)
}
|> castError(SetForumChannelTopicPinnedError.self)
|> mapToSignal { threadIds -> Signal<Never, SetForumChannelTopicPinnedError> in
var threadIds = threadIds
if threadIds.contains(threadId) {
threadIds.removeAll(where: { $0 == threadId })
} else {
if threadIds.count + 1 > 5 {
return .fail(.limitReached(5))
}
threadIds.insert(threadId, at: 0)
}
return _internal_setForumChannelPinnedTopics(account: self.account, id: id, threadIds: threadIds)
}
}
public func getForumChannelPinnedTopics(id: EnginePeer.Id) -> Signal<[Int64], NoError> {
return self.account.postbox.transaction { transcation -> [Int64] in
return transcation.getPeerPinnedThreads(peerId: id)
}
}
public func setForumChannelPinnedTopics(id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, SetForumChannelTopicPinnedError> {
return _internal_setForumChannelPinnedTopics(account: self.account, id: id, threadIds: threadIds)
} }
public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> { public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> {

View File

@ -30,7 +30,7 @@ public extension Peer {
if let restrictionInfo = restrictionInfo { if let restrictionInfo = restrictionInfo {
for rule in restrictionInfo.rules { for rule in restrictionInfo.rules {
if rule.platform == "all" || rule.platform == platform { if rule.platform == "all" || rule.platform == platform || contentSettings.addContentRestrictionReasons.contains(rule.platform) {
if !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) { if !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) {
return rule.text return rule.text
} }

View File

@ -6132,12 +6132,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage
if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with({ $0 })) {
buttonKeyboardMessage = nil
}
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { updated in strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { updated in
var updated = updated var updated = updated
updated = updated.updatedInterfaceState({ _ in return interfaceState }) updated = updated.updatedInterfaceState({ _ in return interfaceState })
updated = updated.updatedKeyboardButtonsMessage(combinedInitialData.buttonKeyboardMessage) updated = updated.updatedKeyboardButtonsMessage(buttonKeyboardMessage)
updated = updated.updatedPinnedMessageId(pinnedMessageId) updated = updated.updatedPinnedMessageId(pinnedMessageId)
updated = updated.updatedPinnedMessage(pinnedMessage) updated = updated.updatedPinnedMessage(pinnedMessage)
updated = updated.updatedPeerIsBlocked(peerIsBlocked) updated = updated.updatedPeerIsBlocked(peerIsBlocked)

View File

@ -1396,7 +1396,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
forceUpdateAll = true forceUpdateAll = true
} }
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: updateAllOnEachVersion || forceUpdateAll) var keyboardButtonsMessage = view.topTaggedMessages.first
if let keyboardButtonsMessageValue = keyboardButtonsMessage, keyboardButtonsMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) {
keyboardButtonsMessage = nil
}
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: updateAllOnEachVersion || forceUpdateAll)
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition) var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition)
if disableAnimations { if disableAnimations {

View File

@ -181,17 +181,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
} }
} }
} }
} else {
if chatPresentationInterfaceState.interfaceState.replyMessageId == nil {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
let panel = ChatRestrictedInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)
}
}
} }
} }
@ -248,6 +237,22 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
} }
} }
} }
if channel.flags.contains(.isForum) {
if let _ = chatPresentationInterfaceState.threadData {
} else {
if chatPresentationInterfaceState.interfaceState.replyMessageId == nil {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
let panel = ChatRestrictedInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)
}
}
}
}
} else if let group = peer as? TelegramGroup { } else if let group = peer as? TelegramGroup {
switch group.membership { switch group.membership {
case .Removed, .Left: case .Removed, .Left:

View File

@ -1167,7 +1167,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else if let threadId = item.message.threadId, Int64(replyAttribute.messageId.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
} else { } else {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,

View File

@ -1418,7 +1418,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} else if let attribute = attribute as? ReplyMessageAttribute { } else if let attribute = attribute as? ReplyMessageAttribute {
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == attribute.messageId { if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == attribute.messageId {
} else if let threadId = firstMessage.threadId, Int64(attribute.messageId.id) == threadId, let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
} else { } else {
replyMessage = firstMessage.associatedMessages[attribute.messageId] replyMessage = firstMessage.associatedMessages[attribute.messageId]
} }

View File

@ -207,7 +207,7 @@ final class ChatMessageAccessibilityData {
if let chatPeer = message.peers[item.message.id.peerId] { if let chatPeer = message.peers[item.message.id.peerId] {
let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
let (_, _, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) let (_, _, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId)
var text = messageText var text = messageText

View File

@ -97,8 +97,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let previousMediaReference = maybeNode?.previousMediaReference let previousMediaReference = maybeNode?.previousMediaReference
return { arguments in return { arguments in
//presentationData, strings, context, type, message, parentMessage, constrainedSize
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
let titleFont = Font.medium(fontSize) let titleFont = Font.medium(fontSize)
let textFont = Font.regular(fontSize) let textFont = Font.regular(fontSize)
@ -114,7 +112,12 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
} }
} }
let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId) var (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
if let threadId = arguments.parentMessage.threadId, Int64(arguments.message.id.id) == threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
titleString = "\(threadInfo.title)"
textString = NSAttributedString()
}
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let titleColor: UIColor let titleColor: UIColor

View File

@ -611,7 +611,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else if let threadId = item.message.threadId, Int64(replyAttribute.messageId.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
} else { } else {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,

View File

@ -621,6 +621,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
if let components = URLComponents(string: "/?" + query) { if let components = URLComponents(string: "/?" + query) {
var channelId: Int64? var channelId: Int64?
var postId: Int32? var postId: Int32?
var threadId: Int64?
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value { if let value = queryItem.value {
@ -628,12 +629,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
channelId = Int64(value) channelId = Int64(value)
} else if queryItem.name == "post" { } else if queryItem.name == "post" {
postId = Int32(value) postId = Int32(value)
} else if queryItem.name == "thread" {
threadId = Int64(value)
} }
} }
} }
} }
if let channelId = channelId, let postId = postId { if let channelId = channelId {
convertedUrl = "https://t.me/c/\(channelId)/\(postId)" if let postId = postId {
if let threadId = threadId {
convertedUrl = "https://t.me/c/\(channelId)/\(threadId)/\(postId)"
} else {
convertedUrl = "https://t.me/c/\(channelId)/\(postId)"
}
} else if let threadId = threadId {
convertedUrl = "https://t.me/c/\(channelId)/\(threadId)"
}
} }
} }
} }
@ -652,6 +663,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
var attach: String? var attach: String?
var startAttach: String? var startAttach: String?
var choose: String? var choose: String?
var threadId: Int64?
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value { if let value = queryItem.value {
@ -677,6 +689,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
startAttach = value startAttach = value
} else if queryItem.name == "choose" { } else if queryItem.name == "choose" {
choose = value choose = value
} else if queryItem.name == "thread" {
threadId = Int64(value)
} }
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
voiceChat = "" voiceChat = ""
@ -694,8 +708,15 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = "https://t.me/+\(phone)" convertedUrl = "https://t.me/+\(phone)"
} else if let domain = domain { } else if let domain = domain {
var result = "https://t.me/\(domain)" var result = "https://t.me/\(domain)"
if let post = post, let postValue = Int(post) { if let threadId = threadId {
result += "/\(postValue)" result += "/\(threadId)"
if let post = post, let postValue = Int(post) {
result += "/\(postValue)"
}
} else {
if let post = post, let postValue = Int(post) {
result += "/\(postValue)"
}
} }
if let start = start { if let start = start {
result += "?start=\(start)" result += "?start=\(start)"

View File

@ -416,7 +416,9 @@ public final class OngoingGroupCallContext {
private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil) private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil)
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) { private let audioSessionActiveDisposable = MetaDisposable()
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) {
self.queue = queue self.queue = queue
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)? var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
@ -571,9 +573,18 @@ public final class OngoingGroupCallContext {
strongSelf.joinPayload.set(.single((payload, ssrc))) strongSelf.joinPayload.set(.single((payload, ssrc)))
} }
}) })
self.audioSessionActiveDisposable.set((audioSessionActive
|> deliverOn(queue)).start(next: { [weak self] isActive in
guard let self else {
return
}
self.context.setManualAudioSessionIsActive(isActive)
}))
} }
deinit { deinit {
self.audioSessionActiveDisposable.dispose()
} }
func setJoinResponse(payload: String) { func setJoinResponse(payload: String) {
@ -936,10 +947,10 @@ public final class OngoingGroupCallContext {
} }
} }
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) { public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) {
let queue = self.queue let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath) return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath)
}) })
} }

View File

@ -326,6 +326,7 @@ private protocol OngoingCallThreadLocalContextProtocol: AnyObject {
func nativeVersion() -> String func nativeVersion() -> String
func nativeGetDerivedState() -> Data func nativeGetDerivedState() -> Data
func addExternalAudioData(data: Data) func addExternalAudioData(data: Data)
func nativeSetIsAudioSessionActive(isActive: Bool)
} }
private final class OngoingCallThreadLocalContextHolder { private final class OngoingCallThreadLocalContextHolder {
@ -381,6 +382,9 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
func addExternalAudioData(data: Data) { func addExternalAudioData(data: Data) {
} }
func nativeSetIsAudioSessionActive(isActive: Bool) {
}
} }
public final class OngoingCallVideoCapturer { public final class OngoingCallVideoCapturer {
@ -573,6 +577,10 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
func addExternalAudioData(data: Data) { func addExternalAudioData(data: Data) {
self.addExternalAudioData(data) self.addExternalAudioData(data)
} }
func nativeSetIsAudioSessionActive(isActive: Bool) {
self.setManualAudioSessionIsActive(isActive)
}
} }
private extension OngoingCallContextState.State { private extension OngoingCallContextState.State {
@ -726,6 +734,7 @@ public final class OngoingCallContext {
} }
private let audioSessionDisposable = MetaDisposable() private let audioSessionDisposable = MetaDisposable()
private let audioSessionActiveDisposable = MetaDisposable()
private var networkTypeDisposable: Disposable? private var networkTypeDisposable: Disposable?
public static var maxLayer: Int32 { public static var maxLayer: Int32 {
@ -886,7 +895,7 @@ public final class OngoingCallContext {
callSessionManager.sendSignalingData(internalId: internalId, data: data) callSessionManager.sendSignalingData(internalId: internalId, data: data)
} }
} }
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "") }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", useManualAudioSessionControl: true)
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in
@ -950,6 +959,16 @@ public final class OngoingCallContext {
self?.audioLevelPromise.set(.single(level)) self?.audioLevelPromise.set(.single(level))
} }
strongSelf.audioSessionActiveDisposable.set((audioSessionActive
|> deliverOn(queue)).start(next: { isActive in
guard let strongSelf = self else {
return
}
strongSelf.withContext { context in
context.nativeSetIsAudioSessionActive(isActive: isActive)
}
}))
strongSelf.networkTypeDisposable = (updatedNetworkType strongSelf.networkTypeDisposable = (updatedNetworkType
|> deliverOn(queue)).start(next: { networkType in |> deliverOn(queue)).start(next: { networkType in
self?.withContext { context in self?.withContext { context in
@ -1010,6 +1029,7 @@ public final class OngoingCallContext {
} }
self.audioSessionDisposable.dispose() self.audioSessionDisposable.dispose()
self.audioSessionActiveDisposable.dispose()
self.networkTypeDisposable?.dispose() self.networkTypeDisposable?.dispose()
} }
@ -1256,6 +1276,8 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
} }
func start() { func start() {
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connecting...")
self.connection.start(queue: self.queue.queue) self.connection.start(queue: self.queue.queue)
self.receivePacketHeader() self.receivePacketHeader()
} }
@ -1399,48 +1421,91 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
} }
private final class CallSignalingConnectionManager { private final class CallSignalingConnectionManager {
private final class ConnectionContext {
let connection: CallSignalingConnection
let host: String
let port: UInt16
init(connection: CallSignalingConnection, host: String, port: UInt16) {
self.connection = connection
self.host = host
self.port = port
}
}
private let queue: Queue private let queue: Queue
private let peerTag: Data
private let dataReceived: (Data) -> Void
private var isRunning: Bool = false
private var nextConnectionId: Int = 0 private var nextConnectionId: Int = 0
private var connections: [Int: CallSignalingConnection] = [:] private var connections: [Int: ConnectionContext] = [:]
init(queue: Queue, peerTag: Data, servers: [OngoingCallConnectionDescriptionWebrtc], dataReceived: @escaping (Data) -> Void) { init(queue: Queue, peerTag: Data, servers: [OngoingCallConnectionDescriptionWebrtc], dataReceived: @escaping (Data) -> Void) {
self.queue = queue self.queue = queue
self.peerTag = peerTag
self.dataReceived = dataReceived
for server in servers { for server in servers {
if server.hasTcp { if server.hasTcp {
let id = self.nextConnectionId self.spawnConnection(host: server.ip, port: UInt16(server.port))
self.nextConnectionId += 1
if #available(iOS 12.0, *) {
let connection = CallSignalingConnectionImpl(queue: queue, host: server.ip, port: UInt16(server.port), peerTag: peerTag, dataReceived: { data in
dataReceived(data)
}, isClosed: { [weak self] in
guard let strongSelf = self else {
return
}
let _ = strongSelf
})
connections[id] = connection
}
} }
} }
} }
func start() { func start() {
if self.isRunning {
return
}
self.isRunning = true
for (_, connection) in self.connections { for (_, connection) in self.connections {
connection.start() connection.connection.start()
} }
} }
func stop() { func stop() {
if !self.isRunning {
return
}
self.isRunning = false
for (_, connection) in self.connections { for (_, connection) in self.connections {
connection.stop() connection.connection.stop()
} }
} }
func send(payloadData: Data) { func send(payloadData: Data) {
for (_, connection) in self.connections { for (_, connection) in self.connections {
connection.send(payloadData: payloadData) connection.connection.send(payloadData: payloadData)
}
}
private func spawnConnection(host: String, port: UInt16) {
let id = self.nextConnectionId
self.nextConnectionId += 1
if #available(iOS 12.0, *) {
let dataReceived = self.dataReceived
let connection = CallSignalingConnectionImpl(queue: queue, host: host, port: port, peerTag: self.peerTag, dataReceived: { data in
dataReceived(data)
}, isClosed: { [weak self] in
guard let self else {
return
}
self.handleConnectionFailed(id: id)
})
self.connections[id] = ConnectionContext(connection: connection, host: host, port: port)
if self.isRunning {
connection.start()
}
}
}
private func handleConnectionFailed(id: Int) {
if let connection = self.connections.removeValue(forKey: id) {
connection.connection.stop()
self.spawnConnection(host: connection.host, port: connection.port)
} }
} }
} }

View File

@ -206,7 +206,24 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
@property (nonatomic, copy) void (^ _Nullable audioLevelUpdated)(float); @property (nonatomic, copy) void (^ _Nullable audioLevelUpdated)(float);
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId; - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
proxy:(VoipProxyServerWebrtc * _Nullable)proxy
networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving
derivedState:(NSData * _Nonnull)derivedState
key:(NSData * _Nonnull)key
isOutgoing:(bool)isOutgoing
connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer
allowP2P:(BOOL)allowP2P
allowTCP:(BOOL)allowTCP
enableStunMarking:(BOOL)enableStunMarking
logPath:(NSString * _Nonnull)logPath
statsLogPath:(NSString * _Nonnull)statsLogPath
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
useManualAudioSessionControl:(bool)useManualAudioSessionControl;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
- (void)beginTermination; - (void)beginTermination;
- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; - (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
@ -360,6 +377,8 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
- (void)stop; - (void)stop;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast; - (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast;
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion; - (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;

View File

@ -28,6 +28,9 @@
#include "platform/darwin/iOS/tgcalls_audio_device_module_ios.h" #include "platform/darwin/iOS/tgcalls_audio_device_module_ios.h"
#include "platform/darwin/iOS/RTCAudioSession.h"
#include "platform/darwin/iOS/RTCAudioSessionConfiguration.h"
#endif #endif
#import "group/GroupInstanceImpl.h" #import "group/GroupInstanceImpl.h"
@ -705,6 +708,8 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls:
id<OngoingCallThreadLocalContextQueueWebrtc> _queue; id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
int32_t _contextId; int32_t _contextId;
bool _useManualAudioSessionControl;
OngoingCallNetworkTypeWebrtc _networkType; OngoingCallNetworkTypeWebrtc _networkType;
NSTimeInterval _callReceiveTimeout; NSTimeInterval _callReceiveTimeout;
NSTimeInterval _callRingTimeout; NSTimeInterval _callRingTimeout;
@ -843,7 +848,22 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId { - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
proxy:(VoipProxyServerWebrtc * _Nullable)proxy
networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving
derivedState:(NSData * _Nonnull)derivedState
key:(NSData * _Nonnull)key
isOutgoing:(bool)isOutgoing
connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer
allowP2P:(BOOL)allowP2P
allowTCP:(BOOL)allowTCP
enableStunMarking:(BOOL)enableStunMarking
logPath:(NSString * _Nonnull)logPath
statsLogPath:(NSString * _Nonnull)statsLogPath
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
useManualAudioSessionControl:(bool)useManualAudioSessionControl {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_version = version; _version = version;
@ -852,6 +872,25 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
assert([[OngoingCallThreadLocalContextWebrtc versionsWithIncludeReference:true] containsObject:version]); assert([[OngoingCallThreadLocalContextWebrtc versionsWithIncludeReference:true] containsObject:version]);
_useManualAudioSessionControl = useManualAudioSessionControl;
[RTCAudioSession sharedInstance].useManualAudio = true;
#ifdef WEBRTC_IOS
RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration];
if (useManualAudioSessionControl) {
sharedConfiguration.mode = AVAudioSessionModeDefault;
} else {
sharedConfiguration.mode = AVAudioSessionModeVoiceChat;
}
sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers;
sharedConfiguration.outputNumberOfChannels = 1;
[RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration];
/*[RTCAudioSession sharedInstance].useManualAudio = true;
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
[RTCAudioSession sharedInstance].isAudioEnabled = true;*/
#endif
_callReceiveTimeout = 20.0; _callReceiveTimeout = 20.0;
_callRingTimeout = 90.0; _callRingTimeout = 90.0;
_callConnectTimeout = 30.0; _callConnectTimeout = 30.0;
@ -1094,6 +1133,17 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
- (void)beginTermination { - (void)beginTermination {
} }
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive {
if (_useManualAudioSessionControl) {
if (isAudioSessionActive) {
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
} else {
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
}
[RTCAudioSession sharedInstance].isAudioEnabled = isAudioSessionActive;
}
}
+ (void)stopWithTerminationResult:(OngoingCallThreadLocalContextWebrtcTerminationResult *)terminationResult completion:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { + (void)stopWithTerminationResult:(OngoingCallThreadLocalContextWebrtcTerminationResult *)terminationResult completion:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion {
if (completion) { if (completion) {
if (terminationResult) { if (terminationResult) {
@ -1429,6 +1479,22 @@ private:
} }
} }
#ifdef WEBRTC_IOS
RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration];
sharedConfiguration.mode = AVAudioSessionModeVoiceChat;
sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers;
if (disableAudioInput) {
sharedConfiguration.outputNumberOfChannels = 2;
} else {
sharedConfiguration.outputNumberOfChannels = 1;
}
[RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration];
/*[RTCAudioSession sharedInstance].useManualAudio = true;
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
[RTCAudioSession sharedInstance].isAudioEnabled = true;*/
#endif
std::vector<tgcalls::VideoCodecName> videoCodecPreferences; std::vector<tgcalls::VideoCodecName> videoCodecPreferences;
int minOutgoingVideoBitrateKbit = 500; int minOutgoingVideoBitrateKbit = 500;
@ -1612,6 +1678,15 @@ private:
} }
} }
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive {
if (isAudioSessionActive) {
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
} else {
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
}
[RTCAudioSession sharedInstance].isAudioEnabled = isAudioSessionActive;
}
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast { - (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast {
if (_instance) { if (_instance) {
tgcalls::GroupConnectionMode mappedConnectionMode; tgcalls::GroupConnectionMode mappedConnectionMode;

@ -1 +1 @@
Subproject commit 53bb1711ae0b3810d34edb1c81982b18d70c5506 Subproject commit 0aa4b1277fd018e56bf194d72b5405e397c6918b