mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Tab support
This commit is contained in:
parent
2505082eee
commit
507e17f40f
@ -95,7 +95,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate/*, TabBarContainedController*/ {
|
||||
public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate, TabBarContainedController {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
public let context: AccountContext
|
||||
@ -133,9 +133,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private let stateDisposable = MetaDisposable()
|
||||
private var filterDisposable: Disposable?
|
||||
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
private let tabContainerNode: ChatListFilterTabContainerNode
|
||||
private var tabContainerData: ([ChatListFilterTabEntry], ChatListFilterTabEntryId)?
|
||||
|
||||
private let chatListFilterValue = Promise<ChatListFilter?>()
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
self.chatListDisplayNode.chatListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition)
|
||||
@ -156,6 +162,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
self.titleView = ChatListTitleView(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
|
||||
self.tabContainerNode = ChatListFilterTabContainerNode()
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary)
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
@ -265,14 +273,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
context.account.networkState,
|
||||
hasProxy,
|
||||
passcode,
|
||||
self.chatListDisplayNode.chatListNode.state,
|
||||
self.chatListDisplayNode.chatListNode.chatListFilterSignal
|
||||
).start(next: { [weak self] networkState, proxy, passcode, state, chatListFilter in
|
||||
self.chatListDisplayNode.chatListNode.state
|
||||
).start(next: { [weak self] networkState, proxy, passcode, state in
|
||||
if let strongSelf = self {
|
||||
let defaultTitle: String
|
||||
if strongSelf.groupId == .root {
|
||||
if let chatListFilter = chatListFilter {
|
||||
let title: String = chatListFilter.title ?? ""
|
||||
if let chatListFilter = strongSelf.filter {
|
||||
let title: String = chatListFilter.title ?? strongSelf.presentationData.strings.DialogList_Title
|
||||
defaultTitle = title
|
||||
} else {
|
||||
defaultTitle = strongSelf.presentationData.strings.DialogList_Title
|
||||
@ -397,6 +404,107 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
preconditionFailure("debug tap")
|
||||
}
|
||||
}
|
||||
|
||||
if self.filter == nil {
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.chatListFilterSettings
|
||||
]))
|
||||
let filterItems = chatListFilterItems(context: context)
|
||||
|> map { items -> [ChatListFilterTabEntry] in
|
||||
var result: [ChatListFilterTabEntry] = []
|
||||
result.append(.all)
|
||||
for (filter, unreadCount) in items {
|
||||
result.append(.filter(id: filter.id, text: filter.title ?? "", unreadCount: unreadCount))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
self.filterDisposable = (combineLatest(queue: .mainQueue(),
|
||||
context.account.postbox.combinedView(keys: [
|
||||
preferencesKey
|
||||
]),
|
||||
filterItems,
|
||||
self.chatListFilterValue.get() |> map { $0?.id } |> distinctUntilChanged
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] combinedView, filterItems, selectedFilter in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var filterSettings: ChatListFilterSettings = .default
|
||||
if let preferencesView = combinedView.views[preferencesKey] as? PreferencesView {
|
||||
if let value = preferencesView.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings {
|
||||
filterSettings = value
|
||||
}
|
||||
}
|
||||
|
||||
var resolvedItems = filterItems
|
||||
if !filterSettings.displayTabs {
|
||||
resolvedItems = []
|
||||
}
|
||||
|
||||
var wasEmpty = false
|
||||
if let tabContainerData = strongSelf.tabContainerData {
|
||||
wasEmpty = tabContainerData.0.count <= 1
|
||||
} else {
|
||||
wasEmpty = true
|
||||
}
|
||||
let selectedEntryId: ChatListFilterTabEntryId = selectedFilter.flatMap { .filter($0) } ?? .all
|
||||
strongSelf.tabContainerData = (resolvedItems, selectedEntryId)
|
||||
|
||||
let isEmpty = resolvedItems.count <= 1
|
||||
|
||||
if wasEmpty != isEmpty {
|
||||
strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode)
|
||||
if let parentController = strongSelf.parent as? TabBarController {
|
||||
parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode)
|
||||
}
|
||||
}
|
||||
|
||||
if let layout = strongSelf.validLayout {
|
||||
if wasEmpty != isEmpty {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
(strongSelf.parent as? TabBarController)?.updateLayout()
|
||||
} else {
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.tabContainerNode.tabSelected = { [weak self] id in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch id {
|
||||
case .all:
|
||||
strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil
|
||||
case let .filter(id):
|
||||
var found = false
|
||||
for filter in filters {
|
||||
if filter.id == id {
|
||||
strongSelf.chatListDisplayNode.chatListNode.chatListFilter = filter
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.tabContainerNode.addFilter = { [weak self] in
|
||||
self?.openFilterSettings()
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -412,6 +520,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self.suggestLocalizationDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.stateDisposable.dispose()
|
||||
self.filterDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
@ -461,6 +570,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChatListControllerNode(context: self.context, groupId: self.groupId, filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, controller: self)
|
||||
self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.chatListFilterSignal)
|
||||
|
||||
self.chatListDisplayNode.navigationBar = self.navigationBar
|
||||
|
||||
@ -726,7 +836,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
}
|
||||
|
||||
self.stateDisposable.set(combineLatest(queue: .mainQueue(), self.presentationDataValue.get(), peerIdsAndOptions).start(next: { [weak self] presentationData, peerIdsAndOptions in
|
||||
self.stateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
self.presentationDataValue.get(),
|
||||
peerIdsAndOptions
|
||||
).start(next: { [weak self] presentationData, peerIdsAndOptions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -735,13 +848,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
if let (options, peerIds) = peerIdsAndOptions {
|
||||
let leftAction: ToolbarAction
|
||||
switch options.read {
|
||||
case let .all(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_ReadAll, isEnabled: enabled)
|
||||
case let .selective(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_Read, isEnabled: enabled)
|
||||
case let .all(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_ReadAll, isEnabled: enabled)
|
||||
case let .selective(enabled):
|
||||
leftAction = ToolbarAction(title: presentationData.strings.ChatList_Read, isEnabled: enabled)
|
||||
}
|
||||
var archiveEnabled = options.delete
|
||||
if archiveEnabled {
|
||||
if archiveEnabled && strongSelf.filter == nil {
|
||||
for peerId in peerIds {
|
||||
if peerId == PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) {
|
||||
archiveEnabled = false
|
||||
@ -919,12 +1032,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
var tabContainerOffset: CGFloat = 0.0
|
||||
if !self.displayNavigationBar {
|
||||
tabContainerOffset += layout.statusBarHeight ?? 0.0
|
||||
tabContainerOffset += 44.0 + 44.0 + 44.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver {
|
||||
searchContentNode.updateListVisibleContentOffset(.known(0.0))
|
||||
self.chatListDisplayNode.chatListNode.scrollToPosition(.top)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, transition: transition)
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
override public func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||
@ -934,7 +1056,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
@objc private func editPressed() {
|
||||
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
||||
if case .root = self.groupId {
|
||||
if case .root = self.groupId, self.filter == nil {
|
||||
self.navigationItem.leftBarButtonItem = editItem
|
||||
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.details, transition: .animated(duration: 0.5, curve: .spring))
|
||||
} else {
|
||||
@ -954,7 +1076,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
@objc private func donePressed() {
|
||||
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
||||
if case .root = self.groupId {
|
||||
if case .root = self.groupId, self.filter == nil {
|
||||
self.navigationItem.leftBarButtonItem = editItem
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = editItem
|
||||
@ -1788,7 +1910,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
override public func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
|
||||
if case .root = self.groupId {
|
||||
if case .root = self.groupId, self.filter == nil {
|
||||
super.setToolbar(toolbar, transition: transition)
|
||||
} else {
|
||||
self.chatListDisplayNode.toolbar = toolbar
|
||||
@ -1804,7 +1926,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
}
|
||||
|
||||
/*public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
|
||||
private func openFilterSettings() {
|
||||
self.push(chatListFilterPresetListController(context: self.context, updated: { _ in
|
||||
}))
|
||||
}
|
||||
|
||||
public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
|
||||
if self.isNodeLoaded {
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
@ -1849,5 +1976,5 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
public func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) {
|
||||
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
|
||||
private(set) var searchDisplayController: SearchDisplayController?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)?
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((Peer, Bool) -> Void)?
|
||||
@ -102,8 +102,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
let chatListEmptyNode = ChatListEmptyNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings)
|
||||
strongSelf.chatListEmptyNode = chatListEmptyNode
|
||||
strongSelf.insertSubnode(chatListEmptyNode, belowSubnode: strongSelf.chatListNode)
|
||||
if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
||||
if let (layout, navigationHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
}
|
||||
strongSelf.isEmptyUpdated?(true)
|
||||
}
|
||||
@ -114,9 +114,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
default:
|
||||
if let chatListEmptyNode = strongSelf.chatListEmptyNode {
|
||||
strongSelf.chatListEmptyNode = nil
|
||||
chatListEmptyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak chatListEmptyNode] _ in
|
||||
chatListEmptyNode?.removeFromSupernode()
|
||||
})
|
||||
chatListEmptyNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
switch isEmptyState {
|
||||
@ -125,8 +123,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
let chatListEmptyIndicator = ActivityIndicator(type: .custom(strongSelf.presentationData.theme.list.itemSecondaryTextColor, 22.0, 1.0, false))
|
||||
strongSelf.chatListEmptyIndicator = chatListEmptyIndicator
|
||||
strongSelf.insertSubnode(chatListEmptyIndicator, belowSubnode: strongSelf.chatListNode)
|
||||
if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
||||
if let (layout, navigationHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -160,8 +158,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight, visualNavigationHeight)
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
@ -233,12 +231,12 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
guard let (containerLayout, navigationBarHeight, _) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
guard let (containerLayout, navigationBarHeight, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -262,7 +260,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
})
|
||||
self.chatListNode.accessibilityElementsHidden = true
|
||||
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
|
||||
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
|
||||
if isSearchBar {
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*import Foundation
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
@ -58,6 +58,7 @@ private enum ChatListFilterPresetEntryStableId: Hashable {
|
||||
private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
case nameHeader(String)
|
||||
case name(placeholder: String, value: String)
|
||||
case typesHeader(text: String)
|
||||
case filterPrivateChats(title: String, value: Bool)
|
||||
case filterSecretChats(title: String, value: Bool)
|
||||
case filterPrivateGroups(title: String, value: Bool)
|
||||
@ -75,7 +76,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case .nameHeader, .name:
|
||||
return ChatListFilterPresetControllerSection.name.rawValue
|
||||
case .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels:
|
||||
case .typesHeader, .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels:
|
||||
return ChatListFilterPresetControllerSection.categories.rawValue
|
||||
case .filterMuted, .filterRead:
|
||||
return ChatListFilterPresetControllerSection.excludeCategories.rawValue
|
||||
@ -90,26 +91,28 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return .index(0)
|
||||
case .name:
|
||||
return .index(1)
|
||||
case .filterPrivateChats:
|
||||
case .typesHeader:
|
||||
return .index(2)
|
||||
case .filterSecretChats:
|
||||
case .filterPrivateChats:
|
||||
return .index(3)
|
||||
case .filterPrivateGroups:
|
||||
case .filterSecretChats:
|
||||
return .index(4)
|
||||
case .filterBots:
|
||||
case .filterPrivateGroups:
|
||||
return .index(5)
|
||||
case .filterPublicGroups:
|
||||
return .index(6)
|
||||
case .filterChannels:
|
||||
return .index(7)
|
||||
case .filterMuted:
|
||||
case .filterBots:
|
||||
return .index(8)
|
||||
case .filterRead:
|
||||
case .filterMuted:
|
||||
return .index(9)
|
||||
case .additionalPeersHeader:
|
||||
case .filterRead:
|
||||
return .index(10)
|
||||
case .addAdditionalPeer:
|
||||
case .additionalPeersHeader:
|
||||
return .index(11)
|
||||
case .addAdditionalPeer:
|
||||
return .index(12)
|
||||
case let .additionalPeer(additionalPeer):
|
||||
return .peer(additionalPeer.peer.peerId)
|
||||
case .additionalPeerInfo:
|
||||
@ -172,6 +175,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return state
|
||||
}
|
||||
}, action: {})
|
||||
case let .typesHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .filterPrivateChats(title, value):
|
||||
return filterEntry(presentationData: presentationData, arguments: arguments, title: title, value: value, filter: .privateChats, section: self.section)
|
||||
case let .filterSecretChats(title, value):
|
||||
@ -243,21 +248,22 @@ private struct ChatListFilterPresetControllerState: Equatable {
|
||||
private func chatListFilterPresetControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetControllerState, peers: [RenderedPeer]) -> [ChatListFilterPresetEntry] {
|
||||
var entries: [ChatListFilterPresetEntry] = []
|
||||
|
||||
entries.append(.nameHeader("NAME"))
|
||||
entries.append(.name(placeholder: "Preset Name", value: state.name))
|
||||
entries.append(.nameHeader("FILTER NAME"))
|
||||
entries.append(.name(placeholder: "Filter Name", value: state.name))
|
||||
|
||||
entries.append(.typesHeader(text: "INCLUDE CHAT TYPES"))
|
||||
entries.append(.filterPrivateChats(title: "Private Chats", value: state.includeCategories.contains(.privateChats)))
|
||||
entries.append(.filterSecretChats(title: "Secret Chats", value: state.includeCategories.contains(.secretChats)))
|
||||
entries.append(.filterPrivateGroups(title: "Private Groups", value: state.includeCategories.contains(.privateGroups)))
|
||||
entries.append(.filterBots(title: "Bots", value: state.includeCategories.contains(.bots)))
|
||||
entries.append(.filterPublicGroups(title: "Public Groups", value: state.includeCategories.contains(.publicGroups)))
|
||||
entries.append(.filterChannels(title: "Channels", value: state.includeCategories.contains(.channels)))
|
||||
entries.append(.filterBots(title: "Bots", value: state.includeCategories.contains(.bots)))
|
||||
|
||||
entries.append(.filterMuted(title: "Exclude Muted", value: state.excludeMuted))
|
||||
entries.append(.filterRead(title: "Exclude Read", value: state.excludeRead))
|
||||
|
||||
entries.append(.additionalPeersHeader("ALWAYS INCLUDE"))
|
||||
entries.append(.addAdditionalPeer(title: "Add"))
|
||||
entries.append(.addAdditionalPeer(title: "Add Chats"))
|
||||
|
||||
for peer in peers {
|
||||
entries.append(.additionalPeer(index: entries.count, peer: peer, isRevealed: state.revealedPeerId == peer.peerId))
|
||||
@ -273,7 +279,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
if let currentPreset = currentPreset {
|
||||
initialName = currentPreset.title ?? ""
|
||||
} else {
|
||||
initialName = "New Preset"
|
||||
initialName = "New Filter"
|
||||
}
|
||||
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.categories ?? .all, excludeMuted: currentPreset?.excludeMuted ?? false, excludeRead: currentPreset?.excludeRead ?? false, additionallyIncludePeers: currentPreset?.includePeers ?? [])
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -365,17 +371,29 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text("Create"), style: .bold, enabled: state.isComplete, action: {
|
||||
let state = stateValue.with { $0 }
|
||||
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers)
|
||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var preset = preset
|
||||
if currentPreset == nil {
|
||||
preset.id = max(2, settings.filters.map({ $0.id }).max() ?? 2)
|
||||
preset.id = max(2, settings.filters.map({ $0.id + 1 }).max() ?? 2)
|
||||
}
|
||||
var settings = settings
|
||||
settings.filters = settings.filters.filter { $0 != preset && $0 != currentPreset }
|
||||
settings.filters.append(preset)
|
||||
if let currentPreset = currentPreset {
|
||||
var found = false
|
||||
for i in 0 ..< settings.filters.count {
|
||||
if settings.filters[i].id == preset.id {
|
||||
settings.filters[i] = preset
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
settings.filters.append(preset)
|
||||
}
|
||||
} else {
|
||||
settings.filters.append(preset)
|
||||
}
|
||||
return settings
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { settings in
|
||||
@ -384,7 +402,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
})
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.SocksProxySetup_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? "Filter" : "Create Filter"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, state: state, peers: statePeers), style: .blocks, emptyStateItem: nil, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
@ -405,4 +423,3 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
return controller
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*import Foundation
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
@ -9,17 +9,20 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import AccountContext
|
||||
import ItemListPeerActionItem
|
||||
|
||||
private final class ChatListFilterPresetListControllerArguments {
|
||||
let context: AccountContext
|
||||
|
||||
let toggleEnableTabs: (Bool) -> Void
|
||||
let openPreset: (ChatListFilter) -> Void
|
||||
let addNew: () -> Void
|
||||
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
|
||||
let removePreset: (Int32) -> Void
|
||||
|
||||
init(context: AccountContext, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) {
|
||||
init(context: AccountContext, toggleEnableTabs: @escaping (Bool) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void) {
|
||||
self.context = context
|
||||
self.toggleEnableTabs = toggleEnableTabs
|
||||
self.openPreset = openPreset
|
||||
self.addNew = addNew
|
||||
self.setItemWithRevealedOptions = setItemWithRevealedOptions
|
||||
@ -28,6 +31,7 @@ private final class ChatListFilterPresetListControllerArguments {
|
||||
}
|
||||
|
||||
private enum ChatListFilterPresetListSection: Int32 {
|
||||
case tabs
|
||||
case list
|
||||
}
|
||||
|
||||
@ -44,6 +48,8 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings
|
||||
}
|
||||
|
||||
private enum ChatListFilterPresetListEntryStableId: Hashable {
|
||||
case displayTabs
|
||||
case displayTabsFooter
|
||||
case listHeader
|
||||
case preset(Int32)
|
||||
case addItem
|
||||
@ -51,13 +57,17 @@ private enum ChatListFilterPresetListEntryStableId: Hashable {
|
||||
}
|
||||
|
||||
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
case displayTabs(String, Bool)
|
||||
case displayTabsFooter(String)
|
||||
case listHeader(String)
|
||||
case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool)
|
||||
case addItem(String)
|
||||
case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
|
||||
case addItem(text: String, isEditing: Bool)
|
||||
case listFooter(String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .displayTabs, .displayTabsFooter:
|
||||
return ChatListFilterPresetListSection.tabs.rawValue
|
||||
case .listHeader, .preset, .addItem, .listFooter:
|
||||
return ChatListFilterPresetListSection.list.rawValue
|
||||
}
|
||||
@ -65,10 +75,14 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
|
||||
var sortId: Int {
|
||||
switch self {
|
||||
case .listHeader:
|
||||
case .displayTabs:
|
||||
return 0
|
||||
case .displayTabsFooter:
|
||||
return 1
|
||||
case .listHeader:
|
||||
return 2
|
||||
case let .preset(preset):
|
||||
return 1 + preset.index
|
||||
return 3 + preset.index
|
||||
case .addItem:
|
||||
return 1000
|
||||
case .listFooter:
|
||||
@ -78,6 +92,10 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: ChatListFilterPresetListEntryStableId {
|
||||
switch self {
|
||||
case .displayTabs:
|
||||
return .displayTabs
|
||||
case .displayTabsFooter:
|
||||
return .displayTabsFooter
|
||||
case .listHeader:
|
||||
return .listHeader
|
||||
case let .preset(preset):
|
||||
@ -96,18 +114,24 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! ChatListFilterPresetListControllerArguments
|
||||
switch self {
|
||||
case let .displayTabs(title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleEnableTabs(value)
|
||||
})
|
||||
case let .displayTabsFooter(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .listHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
case let .preset(index, title, preset, canBeReordered, canBeDeleted):
|
||||
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", editing: ChatListFilterPresetListItemEditing(editable: true, editing: false, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
|
||||
case let .preset(index, title, preset, canBeReordered, canBeDeleted, isEditing):
|
||||
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
|
||||
arguments.openPreset(preset)
|
||||
}, setItemWithRevealedOptions: { lhs, rhs in
|
||||
arguments.setItemWithRevealedOptions(lhs, rhs)
|
||||
}, remove: {
|
||||
arguments.removePreset(preset.id)
|
||||
})
|
||||
case let .addItem(text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
case let .addItem(text, isEditing):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: isEditing, action: {
|
||||
arguments.addNew()
|
||||
})
|
||||
case let .listFooter(text):
|
||||
@ -117,18 +141,22 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
}
|
||||
|
||||
private struct ChatListFilterPresetListControllerState: Equatable {
|
||||
var isEditing: Bool = false
|
||||
var revealedPreset: Int32? = nil
|
||||
}
|
||||
|
||||
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, settings: ChatListFiltersState) -> [ChatListFilterPresetListEntry] {
|
||||
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filtersState: ChatListFiltersState, settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
|
||||
var entries: [ChatListFilterPresetListEntry] = []
|
||||
|
||||
entries.append(.displayTabs("Show Tabs", settings.displayTabs))
|
||||
entries.append(.displayTabsFooter("Display filter tabs on the main screen for quick switching."))
|
||||
|
||||
entries.append(.listHeader("FILTERS"))
|
||||
for preset in settings.filters {
|
||||
entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: settings.filters.count > 1, canBeDeleted: true))
|
||||
for preset in filtersState.filters {
|
||||
entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: filtersState.filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
|
||||
}
|
||||
entries.append(.addItem("Add New"))
|
||||
entries.append(.listFooter("Add custom presets"))
|
||||
entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing))
|
||||
entries.append(.listFooter("Tap \"Edit\" to change the order or delete filters."))
|
||||
|
||||
return entries
|
||||
}
|
||||
@ -145,7 +173,15 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
|
||||
let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in
|
||||
let arguments = ChatListFilterPresetListControllerArguments(context: context, toggleEnableTabs: { value in
|
||||
let _ = context.account.postbox.transaction({ transaction -> Void in
|
||||
let _ = updateChatListFilterSettings(transaction: transaction, { settings in
|
||||
var settings = settings
|
||||
settings.displayTabs = value
|
||||
return settings
|
||||
})
|
||||
}).start()
|
||||
}, openPreset: { preset in
|
||||
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated))
|
||||
}, addNew: {
|
||||
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated))
|
||||
@ -170,7 +206,7 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
})
|
||||
})
|
||||
|
||||
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters, ApplicationSpecificPreferencesKeys.chatListFilterSettings])
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
@ -178,14 +214,33 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
preferences
|
||||
)
|
||||
|> map { presentationData, state, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let settings = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
let filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: {
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
dismissImpl?()
|
||||
})
|
||||
let rightNavigationButton: ItemListNavigationButton
|
||||
if state.isEditing {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.isEditing = false
|
||||
return state
|
||||
}
|
||||
})
|
||||
} else {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.isEditing = true
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Filter Presets"), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, settings: settings), style: .blocks, animateChanges: true)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Filters"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filtersState: filtersState, settings: filterSettings), style: .blocks, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -203,7 +258,62 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ChatListFilterPresetListEntry]) -> Signal<Bool, NoError> in
|
||||
let fromEntry = entries[fromIndex]
|
||||
guard case let .preset(fromFilter) = fromEntry else {
|
||||
return .single(false)
|
||||
}
|
||||
var referenceFilter: ChatListFilter?
|
||||
var beforeAll = false
|
||||
var afterAll = false
|
||||
if toIndex < entries.count {
|
||||
switch entries[toIndex] {
|
||||
case let .preset(toFilter):
|
||||
referenceFilter = toFilter.preset
|
||||
default:
|
||||
if entries[toIndex] < fromEntry {
|
||||
beforeAll = true
|
||||
} else {
|
||||
afterAll = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
afterAll = true
|
||||
}
|
||||
|
||||
return updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { filtersState in
|
||||
var filtersState = filtersState
|
||||
if let index = filtersState.filters.firstIndex(where: { $0.id == fromFilter.preset.id }) {
|
||||
filtersState.filters.remove(at: index)
|
||||
}
|
||||
if let referenceFilter = referenceFilter {
|
||||
var inserted = false
|
||||
for i in 0 ..< filtersState.filters.count {
|
||||
if filtersState.filters[i].id == referenceFilter.id {
|
||||
if fromIndex < toIndex {
|
||||
filtersState.filters.insert(fromFilter.preset, at: i + 1)
|
||||
} else {
|
||||
filtersState.filters.insert(fromFilter.preset, at: i)
|
||||
}
|
||||
inserted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inserted {
|
||||
filtersState.filters.append(fromFilter.preset)
|
||||
}
|
||||
} else if beforeAll {
|
||||
filtersState.filters.insert(fromFilter.preset, at: 0)
|
||||
} else if afterAll {
|
||||
filtersState.filters.append(fromFilter.preset)
|
||||
}
|
||||
return filtersState
|
||||
})
|
||||
|> map { _ -> Bool in
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return controller
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -184,13 +184,13 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
|
||||
let editingOffset: CGFloat = 0.0
|
||||
var editingOffset: CGFloat = 0.0
|
||||
var reorderInset: CGFloat = 0.0
|
||||
|
||||
if item.editing.editing && item.canBeReordered {
|
||||
/*let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
|
||||
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
|
||||
editableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0*/
|
||||
editingOffset = sizeAndApply.0
|
||||
|
||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||
reorderControlSizeAndApply = reorderSizeAndApply
|
||||
|
@ -0,0 +1,336 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SyncCore
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
|
||||
private final class ItemNode: ASDisplayNode {
|
||||
private let pressed: () -> Void
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let badgeTextNode: ImmediateTextNode
|
||||
private let badgeBackgroundNode: ASImageNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
|
||||
private var isSelected: Bool = false
|
||||
private var unreadCount: Int = 0
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
init(pressed: @escaping () -> Void) {
|
||||
self.pressed = pressed
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.badgeTextNode = ImmediateTextNode()
|
||||
self.badgeTextNode.displaysAsynchronously = false
|
||||
|
||||
self.badgeBackgroundNode = ASImageNode()
|
||||
self.badgeBackgroundNode.displaysAsynchronously = false
|
||||
self.badgeBackgroundNode.displayWithoutProcessing = true
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.badgeBackgroundNode)
|
||||
self.addSubnode(self.badgeTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func updateText(title: String, unreadCount: Int, isSelected: Bool, presentationData: PresentationData) {
|
||||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.rootController.navigationBar.badgeBackgroundColor)
|
||||
}
|
||||
|
||||
self.isSelected = isSelected
|
||||
self.unreadCount = unreadCount
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||
if unreadCount != 0 {
|
||||
self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.rootController.navigationBar.badgeTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
|
||||
let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
let badgeInset: CGFloat = 5.0
|
||||
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width + 5.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0))
|
||||
self.badgeBackgroundNode.frame = badgeBackgroundFrame
|
||||
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize)
|
||||
|
||||
if self.unreadCount == 0 {
|
||||
self.badgeBackgroundNode.alpha = 0.0
|
||||
self.badgeTextNode.alpha = 0.0
|
||||
return titleSize.width
|
||||
} else {
|
||||
self.badgeBackgroundNode.alpha = 1.0
|
||||
self.badgeTextNode.alpha = 1.0
|
||||
return badgeBackgroundFrame.maxX
|
||||
}
|
||||
}
|
||||
|
||||
func updateArea(size: CGSize, sideInset: CGFloat) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatListFilterTabEntryId: Hashable {
|
||||
case all
|
||||
case filter(Int32)
|
||||
}
|
||||
|
||||
enum ChatListFilterTabEntry: Equatable {
|
||||
case all
|
||||
case filter(id: Int32, text: String, unreadCount: Int)
|
||||
|
||||
var id: ChatListFilterTabEntryId {
|
||||
switch self {
|
||||
case .all:
|
||||
return .all
|
||||
case let .filter(filter):
|
||||
return .filter(filter.id)
|
||||
}
|
||||
}
|
||||
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .all:
|
||||
return "All Chats"
|
||||
case let .filter(filter):
|
||||
return filter.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class AddItemNode: HighlightableButtonNode {
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
var pressed: (() -> Void)?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
self.addTarget(self, action: #selector(self.onPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func onPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
func update(size: CGSize, theme: PresentationTheme) {
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
self.iconNode.image = PresentationResourcesItemList.plusIconImage(theme)
|
||||
}
|
||||
|
||||
if let image = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
private let scrollNode: ASScrollNode
|
||||
private let selectedLineNode: ASImageNode
|
||||
private var itemNodes: [ChatListFilterTabEntryId: ItemNode] = [:]
|
||||
private let addNode: AddItemNode
|
||||
|
||||
var tabSelected: ((ChatListFilterTabEntryId) -> Void)?
|
||||
var addFilter: (() -> Void)?
|
||||
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData)?
|
||||
|
||||
override init() {
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
self.selectedLineNode = ASImageNode()
|
||||
self.selectedLineNode.displaysAsynchronously = false
|
||||
self.selectedLineNode.displayWithoutProcessing = true
|
||||
|
||||
self.addNode = AddItemNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.scrollsToTop = false
|
||||
self.scrollNode.view.delaysContentTouches = false
|
||||
self.scrollNode.view.canCancelContentTouches = true
|
||||
if #available(iOS 11.0, *) {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.selectedLineNode)
|
||||
self.scrollNode.addSubnode(self.addNode)
|
||||
|
||||
self.addNode.pressed = { [weak self] in
|
||||
self?.addFilter?()
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
let focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
|
||||
|
||||
if self.currentParams?.presentationData.theme !== presentationData.theme {
|
||||
self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
|
||||
})?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 1)
|
||||
}
|
||||
|
||||
self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, presentationData: presentationData)
|
||||
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
for filter in filters {
|
||||
let itemNode: ItemNode
|
||||
var wasAdded = false
|
||||
if let current = self.itemNodes[filter.id] {
|
||||
itemNode = current
|
||||
} else {
|
||||
wasAdded = true
|
||||
itemNode = ItemNode(pressed: { [weak self] in
|
||||
self?.tabSelected?(filter.id)
|
||||
})
|
||||
self.itemNodes[filter.id] = itemNode
|
||||
}
|
||||
let unreadCount: Int
|
||||
switch filter {
|
||||
case .all:
|
||||
unreadCount = 0
|
||||
case let .filter(filter):
|
||||
unreadCount = filter.unreadCount
|
||||
}
|
||||
itemNode.updateText(title: filter.title(strings: presentationData.strings), unreadCount: unreadCount, isSelected: selectedFilter == filter.id, presentationData: presentationData)
|
||||
}
|
||||
var removeKeys: [ChatListFilterTabEntryId] = []
|
||||
for (id, _) in self.itemNodes {
|
||||
if !filters.contains(where: { $0.id == id }) {
|
||||
removeKeys.append(id)
|
||||
}
|
||||
}
|
||||
for id in removeKeys {
|
||||
if let itemNode = self.itemNodes.removeValue(forKey: id) {
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
var tabSizes: [(CGSize, ItemNode, Bool)] = []
|
||||
var totalRawTabSize: CGFloat = 0.0
|
||||
var selectionFrames: [CGRect] = []
|
||||
|
||||
for filter in filters {
|
||||
guard let itemNode = self.itemNodes[filter.id] else {
|
||||
continue
|
||||
}
|
||||
let wasAdded = itemNode.supernode == nil
|
||||
if wasAdded {
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
}
|
||||
let paneNodeWidth = itemNode.updateLayout(height: size.height, transition: transition)
|
||||
let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height)
|
||||
tabSizes.append((paneNodeSize, itemNode, wasAdded))
|
||||
totalRawTabSize += paneNodeSize.width
|
||||
}
|
||||
|
||||
let minSpacing: CGFloat = 30.0
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
var leftOffset: CGFloat = sideInset
|
||||
for i in 0 ..< tabSizes.count {
|
||||
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
||||
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||
if wasAdded {
|
||||
paneNode.frame = paneFrame
|
||||
paneNode.alpha = 0.0
|
||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame)
|
||||
}
|
||||
paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0)
|
||||
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0)
|
||||
|
||||
selectionFrames.append(paneFrame)
|
||||
|
||||
leftOffset += paneNodeSize.width + minSpacing
|
||||
}
|
||||
|
||||
let addSize = CGSize(width: 32.0, height: size.height)
|
||||
transition.updateFrame(node: self.addNode, frame: CGRect(origin: CGPoint(x: max(leftOffset, size.width - sideInset - addSize.width + 6.0), y: 0.0), size: addSize))
|
||||
self.addNode.update(size: addSize, theme: presentationData.theme)
|
||||
leftOffset += addSize.width + minSpacing
|
||||
|
||||
self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset, height: size.height)
|
||||
|
||||
let transitionFraction: CGFloat = 0.0
|
||||
var selectedFrame: CGRect?
|
||||
if let selectedFilter = selectedFilter, let currentIndex = filters.index(where: { $0.id == selectedFilter }) {
|
||||
func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
|
||||
return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t)))
|
||||
}
|
||||
|
||||
if currentIndex != 0 && transitionFraction > 0.0 {
|
||||
let currentFrame = selectionFrames[currentIndex]
|
||||
let previousFrame = selectionFrames[currentIndex - 1]
|
||||
selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction))
|
||||
} else if currentIndex != filters.count - 1 && transitionFraction < 0.0 {
|
||||
let currentFrame = selectionFrames[currentIndex]
|
||||
let previousFrame = selectionFrames[currentIndex + 1]
|
||||
selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction))
|
||||
} else {
|
||||
selectedFrame = selectionFrames[currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if let selectedFrame = selectedFrame {
|
||||
let wasAdded = self.selectedLineNode.isHidden
|
||||
self.selectedLineNode.isHidden = false
|
||||
let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0))
|
||||
if wasAdded {
|
||||
self.selectedLineNode.frame = lineFrame
|
||||
self.selectedLineNode.alpha = 0.0
|
||||
transition.updateAlpha(node: self.selectedLineNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
|
||||
}
|
||||
if focusOnSelectedFilter {
|
||||
if selectedFilter == filters.first?.id {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
||||
} else if selectedFilter == filters.last?.id {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size))
|
||||
} else {
|
||||
let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.selectedLineNode.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import TelegramNotices
|
||||
import ContactsPeerItem
|
||||
import ContextUI
|
||||
import ItemListUI
|
||||
import SearchUI
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList
|
||||
@ -27,6 +28,7 @@ struct ChatListNodeListViewTransition {
|
||||
let options: ListViewDeleteAndInsertOptions
|
||||
let scrollToItem: ListViewScrollToItem?
|
||||
let stationaryItemRange: (Int, Int)?
|
||||
let adjustScrollToFirstItem: Bool
|
||||
}
|
||||
|
||||
final class ChatListHighlightedLocation {
|
||||
@ -281,7 +283,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
}
|
||||
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange)
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem)
|
||||
}
|
||||
|
||||
private final class ChatListOpaqueTransactionState {
|
||||
@ -366,7 +368,17 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
private var currentLocation: ChatListNodeLocation?
|
||||
let chatListFilter: ChatListFilter?
|
||||
var chatListFilter: ChatListFilter? {
|
||||
didSet {
|
||||
self.chatListFilterValue.set(.single(self.chatListFilter))
|
||||
|
||||
if self.chatListFilter != oldValue {
|
||||
if let currentLocation = self.currentLocation {
|
||||
self.setChatListLocation(.initial(count: 50, filter: self.chatListFilter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private let chatListFilterValue = Promise<ChatListFilter?>()
|
||||
var chatListFilterSignal: Signal<ChatListFilter?, NoError> {
|
||||
return self.chatListFilterValue.get()
|
||||
@ -431,6 +443,8 @@ public final class ChatListNode: ListView {
|
||||
self.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
|
||||
self.verticalScrollIndicatorFollowsOverscroll = true
|
||||
|
||||
self.keepMinimalScrollHeightWithTopInset = navigationBarSearchContentHeight
|
||||
|
||||
let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in
|
||||
if let strongSelf = self, let activateSearch = strongSelf.activateSearch {
|
||||
activateSearch()
|
||||
@ -489,6 +503,10 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
})
|
||||
}, setPeerMuted: { [weak self] peerId, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.setCurrentRemovingPeerId(peerId)
|
||||
let _ = (togglePeerMuted(account: context.account, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.updateState { state in
|
||||
@ -496,6 +514,7 @@ public final class ChatListNode: ListView {
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
return state
|
||||
}
|
||||
self?.setCurrentRemovingPeerId(nil)
|
||||
})
|
||||
}, deletePeer: { [weak self] peerId in
|
||||
self?.deletePeerChat?(peerId)
|
||||
@ -505,7 +524,7 @@ public final class ChatListNode: ListView {
|
||||
guard let context = context else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.setCurrentRemovingPeerId(peerId)
|
||||
let _ = (togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.updateState { state in
|
||||
@ -513,6 +532,7 @@ public final class ChatListNode: ListView {
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
return state
|
||||
}
|
||||
self?.setCurrentRemovingPeerId(nil)
|
||||
})
|
||||
}, toggleArchivedFolderHiddenByDefault: { [weak self] in
|
||||
self?.toggleArchivedFolderHiddenByDefault?()
|
||||
@ -1279,7 +1299,19 @@ public final class ChatListNode: ListView {
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
}
|
||||
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion)
|
||||
var scrollToItem = transition.scrollToItem
|
||||
if transition.adjustScrollToFirstItem {
|
||||
var offset: CGFloat = 0.0
|
||||
switch self.visibleContentOffset() {
|
||||
case let .known(value) where abs(value) < .ulpOfOne:
|
||||
offset = 0.0
|
||||
default:
|
||||
offset = -navigationBarSearchContentHeight
|
||||
}
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ struct ChatListNodeViewTransition {
|
||||
let options: ListViewDeleteAndInsertOptions
|
||||
let scrollToItem: ListViewScrollToItem?
|
||||
let stationaryItemRange: (Int, Int)?
|
||||
let adjustScrollToFirstItem: Bool
|
||||
}
|
||||
|
||||
enum ChatListNodeViewScrollPosition {
|
||||
@ -176,11 +177,12 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
|
||||
fromEmptyView = true
|
||||
}
|
||||
|
||||
var adjustScrollToFirstItem = false
|
||||
if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 1 {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
adjustScrollToFirstItem = true
|
||||
}
|
||||
|
||||
subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange))
|
||||
subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, adjustScrollToFirstItem: adjustScrollToFirstItem))
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
|
@ -304,6 +304,123 @@ private final class FilterItemNode: ASDisplayNode, AbstractTabBarChatListFilterI
|
||||
}
|
||||
}
|
||||
|
||||
func chatListFilterItems(context: AccountContext) -> Signal<[(ChatListFilter, Int)], NoError> {
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: [PreferencesKeys.chatListFilters])
|
||||
return context.account.postbox.combinedView(keys: [preferencesKey])
|
||||
|> map { combinedView -> [ChatListFilter] in
|
||||
if let filtersState = (combinedView.views[preferencesKey] as? PreferencesView)?.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState {
|
||||
return filtersState.filters
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
|
||||
var unreadCountItems: [UnreadMessageCountsItem] = []
|
||||
unreadCountItems.append(.total(nil))
|
||||
var additionalPeerIds = Set<PeerId>()
|
||||
for filter in filters {
|
||||
additionalPeerIds.formUnion(filter.includePeers)
|
||||
}
|
||||
if !additionalPeerIds.isEmpty {
|
||||
for peerId in additionalPeerIds {
|
||||
unreadCountItems.append(.peer(peerId))
|
||||
}
|
||||
}
|
||||
let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems)
|
||||
var keys: [PostboxViewKey] = []
|
||||
keys.append(unreadKey)
|
||||
for peerId in additionalPeerIds {
|
||||
keys.append(.basicPeer(peerId))
|
||||
}
|
||||
|
||||
return context.account.postbox.combinedView(keys: keys)
|
||||
|> map { view -> [(ChatListFilter, Int)] in
|
||||
guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else {
|
||||
return []
|
||||
}
|
||||
|
||||
var result: [(ChatListFilter, Int)] = []
|
||||
|
||||
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int)] = [:]
|
||||
|
||||
var totalState: ChatListTotalUnreadState?
|
||||
for entry in unreadCounts.entries {
|
||||
switch entry {
|
||||
case let .total(_, totalStateValue):
|
||||
totalState = totalStateValue
|
||||
case let .peer(peerId, state):
|
||||
if let state = state, state.isUnread {
|
||||
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer {
|
||||
let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer)
|
||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState {
|
||||
peerTagAndCount[peerId] = (tag, 0)
|
||||
} else {
|
||||
var peerCount = Int(state.count)
|
||||
if state.isUnread {
|
||||
peerCount = max(1, peerCount)
|
||||
}
|
||||
peerTagAndCount[peerId] = (tag, peerCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var totalUnreadChatCount = 0
|
||||
if let totalState = totalState {
|
||||
for (_, counters) in totalState.filteredCounters {
|
||||
totalUnreadChatCount += Int(counters.chatCount)
|
||||
}
|
||||
}
|
||||
|
||||
var shouldUpdateLayout = false
|
||||
for filter in filters {
|
||||
var tags: [PeerSummaryCounterTags] = []
|
||||
if filter.categories.contains(.privateChats) {
|
||||
tags.append(.privateChat)
|
||||
}
|
||||
if filter.categories.contains(.secretChats) {
|
||||
tags.append(.secretChat)
|
||||
}
|
||||
if filter.categories.contains(.privateGroups) {
|
||||
tags.append(.privateGroup)
|
||||
}
|
||||
if filter.categories.contains(.bots) {
|
||||
tags.append(.bot)
|
||||
}
|
||||
if filter.categories.contains(.publicGroups) {
|
||||
tags.append(.publicGroup)
|
||||
}
|
||||
if filter.categories.contains(.channels) {
|
||||
tags.append(.channel)
|
||||
}
|
||||
|
||||
var count = 0
|
||||
if let totalState = totalState {
|
||||
for tag in tags {
|
||||
if let value = totalState.filteredCounters[tag] {
|
||||
count += Int(value.chatCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
for peerId in filter.includePeers {
|
||||
if let (tag, peerCount) = peerTagAndCount[peerId] {
|
||||
if !tags.contains(tag) {
|
||||
if peerCount != 0 {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append((filter, count))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class TabBarChatListFilterControllerNode: ViewControllerTracingNode {
|
||||
private let presentationData: PresentationData
|
||||
private let cancel: () -> Void
|
||||
|
@ -168,6 +168,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
}
|
||||
|
||||
public final var keepMinimalScrollHeightWithTopInset: CGFloat?
|
||||
|
||||
public final var stackFromBottom: Bool = false
|
||||
public final var stackFromBottomInsetItemFactor: CGFloat = 0.0
|
||||
public final var limitHitTestToNodes: Bool = false
|
||||
@ -986,6 +988,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
}
|
||||
|
||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||
}
|
||||
|
||||
var transition: ContainedViewLayoutTransition = .immediate
|
||||
if let updateSizeAndInsets = updateSizeAndInsets {
|
||||
if !updateSizeAndInsets.duration.isZero && !isExperimentalSnapToScrollToItem {
|
||||
@ -1383,6 +1390,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
completeHeight += itemNode.apparentBounds.height
|
||||
}
|
||||
|
||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||
}
|
||||
|
||||
if self.stackFromBottom {
|
||||
let updatedCompleteHeight = max(completeHeight, self.visibleSize.height)
|
||||
let deltaCompleteHeight = updatedCompleteHeight - completeHeight
|
||||
|
@ -105,7 +105,7 @@ enum NavigationPreviousAction: Equatable {
|
||||
open class NavigationBar: ASDisplayNode {
|
||||
private var presentationData: NavigationBarPresentationData
|
||||
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, Bool)?
|
||||
private var requestedLayout: Bool = false
|
||||
var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
|
||||
|
||||
@ -124,6 +124,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
private let clippingNode: ASDisplayNode
|
||||
|
||||
public private(set) var contentNode: NavigationBarContentNode?
|
||||
public private(set) var secondaryContentNode: ASDisplayNode?
|
||||
|
||||
private var itemTitleListenerKey: Int?
|
||||
private var itemTitleViewListenerKey: Int?
|
||||
@ -801,16 +802,22 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
if let validLayout = self.validLayout, self.requestedLayout {
|
||||
self.requestedLayout = false
|
||||
self.updateLayout(size: validLayout.0, defaultHeight: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.0, defaultHeight: validLayout.1, additionalHeight: validLayout.2, leftInset: validLayout.3, rightInset: validLayout.4, appearsHidden: validLayout.5, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, defaultHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
if self.layoutSuspended {
|
||||
return
|
||||
}
|
||||
|
||||
self.validLayout = (size, defaultHeight, leftInset, rightInset)
|
||||
self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden)
|
||||
|
||||
if let secondaryContentNode = self.secondaryContentNode {
|
||||
transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? 46.0 : 0.0
|
||||
|
||||
let leftButtonInset: CGFloat = leftInset + 16.0
|
||||
let backButtonInset: CGFloat = leftInset + 27.0
|
||||
@ -818,14 +825,19 @@ open class NavigationBar: ASDisplayNode {
|
||||
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
var expansionHeight: CGFloat = 0.0
|
||||
if let contentNode = self.contentNode {
|
||||
let contentNodeFrame: CGRect
|
||||
var contentNodeFrame: CGRect
|
||||
switch contentNode.mode {
|
||||
case .replacement:
|
||||
expansionHeight = contentNode.height - defaultHeight
|
||||
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||
case .expansion:
|
||||
expansionHeight = contentNode.height
|
||||
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight), size: CGSize(width: size.width, height: expansionHeight))
|
||||
case .replacement:
|
||||
expansionHeight = contentNode.height - defaultHeight
|
||||
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||
case .expansion:
|
||||
expansionHeight = contentNode.height
|
||||
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight), size: CGSize(width: size.width, height: expansionHeight))
|
||||
if appearsHidden {
|
||||
if self.secondaryContentNode != nil {
|
||||
contentNodeFrame.origin.y += 46.0
|
||||
}
|
||||
}
|
||||
}
|
||||
transition.updateFrame(node: contentNode, frame: contentNodeFrame)
|
||||
contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
||||
@ -833,8 +845,8 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel))
|
||||
|
||||
let nominalHeight: CGFloat = defaultHeight
|
||||
let contentVerticalOrigin = size.height - nominalHeight - expansionHeight
|
||||
let nominalHeight: CGFloat = defaultHeight - additionalHeight
|
||||
let contentVerticalOrigin = size.height - nominalHeight - expansionHeight - additionalHeight - apparentAdditionalHeight
|
||||
|
||||
var leftTitleInset: CGFloat = leftInset + 1.0
|
||||
var rightTitleInset: CGFloat = rightInset + 1.0
|
||||
@ -1034,7 +1046,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
let node = NavigationButtonNode()
|
||||
node.updateManualText(self.backButtonNode.manualText)
|
||||
node.color = accentColor
|
||||
if let (size, defaultHeight, _, _) = self.validLayout {
|
||||
if let (size, defaultHeight, _, _, _, _) = self.validLayout {
|
||||
node.updateLayout(constrainedSize: CGSize(width: size.width, height: defaultHeight))
|
||||
node.frame = self.backButtonNode.frame
|
||||
}
|
||||
@ -1057,7 +1069,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
node.updateItems(items)
|
||||
node.color = accentColor
|
||||
if let (size, defaultHeight, _, _) = self.validLayout {
|
||||
if let (size, defaultHeight, _, _, _, _) = self.validLayout {
|
||||
node.updateLayout(constrainedSize: CGSize(width: size.width, height: defaultHeight))
|
||||
node.frame = self.backButtonNode.frame
|
||||
}
|
||||
@ -1103,16 +1115,23 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func contentHeight(defaultHeight: CGFloat) -> CGFloat {
|
||||
var result: CGFloat = 0.0
|
||||
if let contentNode = self.contentNode {
|
||||
switch contentNode.mode {
|
||||
case .expansion:
|
||||
return defaultHeight + contentNode.height
|
||||
case .replacement:
|
||||
return contentNode.height
|
||||
case .expansion:
|
||||
result += defaultHeight + contentNode.height
|
||||
case .replacement:
|
||||
result += contentNode.height
|
||||
}
|
||||
} else {
|
||||
return defaultHeight
|
||||
result += defaultHeight
|
||||
}
|
||||
|
||||
if let _ = self.secondaryContentNode {
|
||||
result += 46.0
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) {
|
||||
@ -1164,6 +1183,18 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func setSecondaryContentNode(_ secondatryContentNode: ASDisplayNode?) {
|
||||
if self.secondaryContentNode !== secondatryContentNode {
|
||||
if let previous = self.secondaryContentNode {
|
||||
previous.removeFromSupernode()
|
||||
}
|
||||
self.secondaryContentNode = secondatryContentNode
|
||||
if let secondaryContentNode = secondatryContentNode {
|
||||
self.clippingNode.addSubnode(secondaryContentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func executeBack() -> Bool {
|
||||
if self.backButtonNode.isInHierarchy {
|
||||
self.backButtonNode.pressed(0)
|
||||
|
@ -269,6 +269,7 @@ open class TabBarController: ViewController {
|
||||
currentController.navigationItem.setTarget(self.navigationItem)
|
||||
displayNavigationBar = currentController.displayNavigationBar
|
||||
self.navigationBar?.setContentNode(currentController.navigationBar?.contentNode, animated: false)
|
||||
self.navigationBar?.setSecondaryContentNode(currentController.navigationBar?.secondaryContentNode)
|
||||
currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle
|
||||
} else {
|
||||
@ -278,14 +279,21 @@ open class TabBarController: ViewController {
|
||||
self.navigationItem.titleView = nil
|
||||
self.navigationItem.backBarButtonItem = nil
|
||||
self.navigationBar?.setContentNode(nil, animated: false)
|
||||
self.navigationBar?.setSecondaryContentNode(nil)
|
||||
displayNavigationBar = false
|
||||
}
|
||||
if self.displayNavigationBar != displayNavigationBar {
|
||||
self.setDisplayNavigationBar(displayNavigationBar)
|
||||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout() {
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,6 +213,21 @@ public enum ViewControllerNavigationPresentation {
|
||||
}
|
||||
}
|
||||
|
||||
open var cleanNavigationHeight: CGFloat {
|
||||
if let navigationBar = self.navigationBar {
|
||||
var height = navigationBar.frame.maxY
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
|
||||
height += contentNode.nominalHeight - contentNode.height
|
||||
}
|
||||
if navigationBar.secondaryContentNode != nil {
|
||||
//height -= 46.0
|
||||
}
|
||||
return height
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
open var visualNavigationInsetHeight: CGFloat {
|
||||
if let navigationBar = self.navigationBar {
|
||||
let height = navigationBar.frame.maxY
|
||||
@ -225,6 +240,8 @@ public enum ViewControllerNavigationPresentation {
|
||||
}
|
||||
}
|
||||
|
||||
public var additionalNavigationBarHeight: CGFloat = 0.0
|
||||
|
||||
private let _ready = Promise<Bool>(true)
|
||||
open var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
@ -338,7 +355,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
|
||||
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
let defaultNavigationBarHeight: CGFloat
|
||||
var defaultNavigationBarHeight: CGFloat
|
||||
if self._presentedInModal {
|
||||
defaultNavigationBarHeight = 56.0
|
||||
} else {
|
||||
@ -353,9 +370,6 @@ public enum ViewControllerNavigationPresentation {
|
||||
navigationBarOffset = 0.0
|
||||
}
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||
if layout.statusBarHeight == nil {
|
||||
//navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
|
||||
}
|
||||
|
||||
if !self.displayNavigationBar {
|
||||
navigationBarFrame.origin.y = -navigationBarFrame.size.height
|
||||
@ -368,7 +382,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar {
|
||||
navigationBarFrame.origin.y += contentNode.height + statusBarHeight
|
||||
}
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, additionalHeight: 0.0, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, transition: transition)
|
||||
if !transition.isAnimated {
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "bounds")
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "position")
|
||||
|
@ -165,7 +165,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
||||
case .generic:
|
||||
verticalInset = 11.0
|
||||
verticalOffset = 0.0
|
||||
leftInset = 59.0 + params.leftInset
|
||||
leftInset = (item.icon == nil ? 16.0 : 59.0) + params.leftInset
|
||||
case .peerList:
|
||||
verticalInset = 14.0
|
||||
verticalOffset = 0.0
|
||||
|
@ -442,6 +442,9 @@
|
||||
SSubscriber *subscriber = context.subscriber;
|
||||
[context.videoProcessor startWithTimeRange:context.timeRange progressBlock:^(CGFloat progress)
|
||||
{
|
||||
#if DEBUG
|
||||
printf("Video progress: %f\n", progress);
|
||||
#endif
|
||||
[subscriber putNext:@(progress)];
|
||||
} completionBlock:^
|
||||
{
|
||||
@ -1032,7 +1035,11 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
|
||||
|
||||
NSDictionary *codecSettings = @
|
||||
{
|
||||
#if DEBUG
|
||||
AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 500),
|
||||
#else
|
||||
AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 1000),
|
||||
#endif
|
||||
AVVideoCleanApertureKey: videoCleanApertureSettings,
|
||||
AVVideoPixelAspectRatioKey: videoAspectRatioSettings
|
||||
};
|
||||
|
@ -351,7 +351,7 @@ final class FFMpegMediaFrameSourceContext: NSObject {
|
||||
|
||||
let avFormatContext = FFMpegAVFormatContext()
|
||||
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(self.ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, seek: seekCallback) else {
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(self.ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback) else {
|
||||
self.readingError = true
|
||||
return
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ public final class SoftwareVideoSource {
|
||||
|
||||
let ioBufferSize = 64 * 1024
|
||||
|
||||
let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, seek: seekCallback)
|
||||
let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback)
|
||||
self.avIoContext = avIoContext
|
||||
|
||||
avFormatContext.setIO(self.avIoContext!)
|
||||
|
@ -123,7 +123,7 @@ private final class UniversalSoftwareVideoSourceImpl {
|
||||
|
||||
let ioBufferSize = 1 * 1024
|
||||
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, seek: seekCallback) else {
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback) else {
|
||||
return nil
|
||||
}
|
||||
self.avIoContext = avIoContext
|
||||
|
@ -3,10 +3,12 @@ import Foundation
|
||||
final class MutableBasicPeerView: MutablePostboxView {
|
||||
private let peerId: PeerId
|
||||
fileprivate var peer: Peer?
|
||||
fileprivate var notificationSettings: PeerNotificationSettings?
|
||||
|
||||
init(postbox: Postbox, peerId: PeerId) {
|
||||
self.peerId = peerId
|
||||
self.peer = postbox.peerTable.get(peerId)
|
||||
self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId)
|
||||
}
|
||||
|
||||
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
|
||||
@ -15,6 +17,10 @@ final class MutableBasicPeerView: MutablePostboxView {
|
||||
self.peer = peer
|
||||
updated = true
|
||||
}
|
||||
if transaction.currentUpdatedPeerNotificationSettings[self.peerId] != nil {
|
||||
self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId)
|
||||
updated = true
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
@ -26,8 +32,10 @@ final class MutableBasicPeerView: MutablePostboxView {
|
||||
|
||||
public final class BasicPeerView: PostboxView {
|
||||
public let peer: Peer?
|
||||
public let notificationSettings: PeerNotificationSettings?
|
||||
|
||||
init(_ view: MutableBasicPeerView) {
|
||||
self.peer = view.peer
|
||||
self.notificationSettings = view.notificationSettings
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-206066487] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||
dict[461151667] = { return Api.ChatFull.parse_chatFull($0) }
|
||||
dict[763976820] = { return Api.ChatFull.parse_channelFull($0) }
|
||||
dict[-253335766] = { return Api.ChatFull.parse_channelFull($0) }
|
||||
dict[-932174686] = { return Api.PollResults.parse_pollResults($0) }
|
||||
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }
|
||||
dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
|
||||
@ -247,6 +247,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
|
||||
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
|
||||
dict[1123585836] = { return Api.Update.parse_updateMessagePollVote($0) }
|
||||
dict[654302845] = { return Api.Update.parse_updateDialogFilter($0) }
|
||||
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
|
||||
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
|
||||
@ -357,6 +360,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
|
||||
dict[-833715459] = { return Api.InputMedia.parse_inputMediaGeoLive($0) }
|
||||
dict[-1410741723] = { return Api.InputMedia.parse_inputMediaPoll($0) }
|
||||
dict[-1358977017] = { return Api.InputMedia.parse_inputMediaDice($0) }
|
||||
dict[2134579434] = { return Api.InputPeer.parse_inputPeerEmpty($0) }
|
||||
dict[2107670217] = { return Api.InputPeer.parse_inputPeerSelf($0) }
|
||||
dict[396093539] = { return Api.InputPeer.parse_inputPeerChat($0) }
|
||||
@ -412,6 +416,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) }
|
||||
dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) }
|
||||
dict[-1269012015] = { return Api.messages.AffectedHistory.parse_affectedHistory($0) }
|
||||
dict[1244130093] = { return Api.StatsGraph.parse_statsGraphAsync($0) }
|
||||
dict[-1092839390] = { return Api.StatsGraph.parse_statsGraphError($0) }
|
||||
dict[-1057809608] = { return Api.StatsGraph.parse_statsGraph($0) }
|
||||
dict[-1036572727] = { return Api.account.PasswordInputSettings.parse_passwordInputSettings($0) }
|
||||
dict[878078826] = { return Api.PageTableCell.parse_pageTableCell($0) }
|
||||
dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) }
|
||||
@ -480,6 +487,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-668391402] = { return Api.InputUser.parse_inputUser($0) }
|
||||
dict[-1366746132] = { return Api.Page.parse_page($0) }
|
||||
dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) }
|
||||
dict[-875679776] = { return Api.StatsPercentValue.parse_statsPercentValue($0) }
|
||||
dict[157948117] = { return Api.upload.File.parse_file($0) }
|
||||
dict[-242427324] = { return Api.upload.File.parse_fileCdnRedirect($0) }
|
||||
dict[-1078612597] = { return Api.ChannelLocation.parse_channelLocationEmpty($0) }
|
||||
@ -506,6 +514,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
|
||||
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
|
||||
dict[-1564789301] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
|
||||
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
|
||||
dict[-1567175714] = { return Api.MessageFwdAuthor.parse_messageFwdAuthor($0) }
|
||||
dict[-1539849235] = { return Api.WallPaper.parse_wallPaper($0) }
|
||||
dict[-1963717851] = { return Api.WallPaper.parse_wallPaperNoFile($0) }
|
||||
@ -520,6 +529,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1837345356] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) }
|
||||
dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) }
|
||||
dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) }
|
||||
dict[205195937] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) }
|
||||
dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) }
|
||||
dict[-1857044719] = { return Api.Updates.parse_updateShortMessage($0) }
|
||||
dict[377562760] = { return Api.Updates.parse_updateShortChatMessage($0) }
|
||||
@ -527,6 +537,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) }
|
||||
dict[1957577280] = { return Api.Updates.parse_updates($0) }
|
||||
dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) }
|
||||
dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) }
|
||||
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
|
||||
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
|
||||
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
||||
@ -539,6 +550,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1666158377] = { return Api.MessageMedia.parse_messageMediaDocument($0) }
|
||||
dict[-873313984] = { return Api.MessageMedia.parse_messageMediaContact($0) }
|
||||
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
||||
dict[1670374507] = { return Api.MessageMedia.parse_messageMediaDice($0) }
|
||||
dict[-842892769] = { return Api.PaymentSavedCredentials.parse_paymentSavedCredentialsCard($0) }
|
||||
dict[1450380236] = { return Api.Null.parse_null($0) }
|
||||
dict[1923290508] = { return Api.auth.CodeType.parse_codeTypeSms($0) }
|
||||
@ -590,6 +602,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
|
||||
dict[-57668565] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) }
|
||||
dict[1061556205] = { return Api.ChatParticipants.parse_chatParticipants($0) }
|
||||
dict[351868460] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
|
||||
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
|
||||
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
|
||||
@ -762,6 +775,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1041346555] = { return Api.updates.ChannelDifference.parse_channelDifferenceEmpty($0) }
|
||||
dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) }
|
||||
dict[-1531132162] = { return Api.updates.ChannelDifference.parse_channelDifferenceTooLong($0) }
|
||||
dict[-581804346] = { return Api.StatsRowAbsValueAndPrev.parse_statsRowAbsValueAndPrev($0) }
|
||||
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
|
||||
dict[-264117680] = { return Api.ChatOnlines.parse_chatOnlines($0) }
|
||||
dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) }
|
||||
@ -1085,6 +1099,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.AffectedHistory:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsGraph:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.account.PasswordInputSettings:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PageTableCell:
|
||||
@ -1155,6 +1171,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.SecureCredentialsEncrypted:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsPercentValue:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.upload.File:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChannelLocation:
|
||||
@ -1189,6 +1207,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PhoneCallProtocol:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsDateRangeDays:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MessageFwdAuthor:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.WallPaper:
|
||||
@ -1205,8 +1225,12 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentCharge:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.stats.BroadcastStats:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.Updates:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsAbsValueAndPrev:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MessageMedia:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentSavedCredentials:
|
||||
@ -1257,6 +1281,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatParticipants:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.DialogFilter:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputPaymentCredentials:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ShippingOption:
|
||||
@ -1387,6 +1413,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.updates.ChannelDifference:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsRowAbsValueAndPrev:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.channels.AdminLogResults:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatOnlines:
|
||||
|
@ -1805,7 +1805,7 @@ public extension Api {
|
||||
}
|
||||
public enum ChatFull: TypeConstructorDescription {
|
||||
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?)
|
||||
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, pts: Int32)
|
||||
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -1828,9 +1828,9 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let pts):
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts):
|
||||
if boxed {
|
||||
buffer.appendInt32(763976820)
|
||||
buffer.appendInt32(-253335766)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
@ -1861,6 +1861,7 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 15) != 0 {location!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 17) != 0 {serializeInt32(slowmodeSeconds!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 18) != 0 {serializeInt32(slowmodeNextSendDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 12) != 0 {serializeInt32(statsDc!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
@ -1870,8 +1871,8 @@ public extension Api {
|
||||
switch self {
|
||||
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId):
|
||||
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId)])
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let pts):
|
||||
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("pts", pts)])
|
||||
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts):
|
||||
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1987,7 +1988,9 @@ public extension Api {
|
||||
var _25: Int32?
|
||||
if Int(_1!) & Int(1 << 18) != 0 {_25 = reader.readInt32() }
|
||||
var _26: Int32?
|
||||
_26 = reader.readInt32()
|
||||
if Int(_1!) & Int(1 << 12) != 0 {_26 = reader.readInt32() }
|
||||
var _27: Int32?
|
||||
_27 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -2013,9 +2016,10 @@ public extension Api {
|
||||
let _c23 = (Int(_1!) & Int(1 << 15) == 0) || _23 != nil
|
||||
let _c24 = (Int(_1!) & Int(1 << 17) == 0) || _24 != nil
|
||||
let _c25 = (Int(_1!) & Int(1 << 18) == 0) || _25 != nil
|
||||
let _c26 = _26 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 {
|
||||
return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, pts: _26!)
|
||||
let _c26 = (Int(_1!) & Int(1 << 12) == 0) || _26 != nil
|
||||
let _c27 = _27 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 {
|
||||
return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -5861,6 +5865,9 @@ public extension Api {
|
||||
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
|
||||
case updateLoginToken
|
||||
case updateMessagePollVote(pollId: Int64, userId: Int32, options: [Buffer])
|
||||
case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?)
|
||||
case updateDialogFilterOrder(order: [Int32])
|
||||
case updateDialogFilters
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -6509,6 +6516,30 @@ public extension Api {
|
||||
for item in options {
|
||||
serializeBytes(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
if boxed {
|
||||
buffer.appendInt32(654302845)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)}
|
||||
break
|
||||
case .updateDialogFilterOrder(let order):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1512627963)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(order.count))
|
||||
for item in order {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .updateDialogFilters:
|
||||
if boxed {
|
||||
buffer.appendInt32(889491791)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -6669,6 +6700,12 @@ public extension Api {
|
||||
return ("updateLoginToken", [])
|
||||
case .updateMessagePollVote(let pollId, let userId, let options):
|
||||
return ("updateMessagePollVote", [("pollId", pollId), ("userId", userId), ("options", options)])
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
return ("updateDialogFilter", [("flags", flags), ("id", id), ("filter", filter)])
|
||||
case .updateDialogFilterOrder(let order):
|
||||
return ("updateDialogFilterOrder", [("order", order)])
|
||||
case .updateDialogFilters:
|
||||
return ("updateDialogFilters", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -7965,6 +8002,41 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Api.DialogFilter?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.DialogFilter
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? {
|
||||
var _1: [Int32]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Update.updateDialogFilterOrder(order: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? {
|
||||
return Api.Update.updateDialogFilters
|
||||
}
|
||||
|
||||
}
|
||||
public enum PopularContact: TypeConstructorDescription {
|
||||
@ -10365,6 +10437,7 @@ public extension Api {
|
||||
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
|
||||
case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, period: Int32?)
|
||||
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?)
|
||||
case inputMediaDice
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -10511,6 +10584,12 @@ public extension Api {
|
||||
for item in correctAnswers! {
|
||||
serializeBytes(item, buffer: buffer, boxed: false)
|
||||
}}
|
||||
break
|
||||
case .inputMediaDice:
|
||||
if boxed {
|
||||
buffer.appendInt32(-1358977017)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -10547,6 +10626,8 @@ public extension Api {
|
||||
return ("inputMediaGeoLive", [("flags", flags), ("geoPoint", geoPoint), ("period", period)])
|
||||
case .inputMediaPoll(let flags, let poll, let correctAnswers):
|
||||
return ("inputMediaPoll", [("flags", flags), ("poll", poll), ("correctAnswers", correctAnswers)])
|
||||
case .inputMediaDice:
|
||||
return ("inputMediaDice", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -10855,6 +10936,9 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputMediaDice(_ reader: BufferReader) -> InputMedia? {
|
||||
return Api.InputMedia.inputMediaDice
|
||||
}
|
||||
|
||||
}
|
||||
public enum InputPeer: TypeConstructorDescription {
|
||||
@ -11956,6 +12040,82 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsGraph: TypeConstructorDescription {
|
||||
case statsGraphAsync(token: String)
|
||||
case statsGraphError(error: String)
|
||||
case statsGraph(json: Api.DataJSON)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsGraphAsync(let token):
|
||||
if boxed {
|
||||
buffer.appendInt32(1244130093)
|
||||
}
|
||||
serializeString(token, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .statsGraphError(let error):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1092839390)
|
||||
}
|
||||
serializeString(error, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .statsGraph(let json):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1057809608)
|
||||
}
|
||||
json.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsGraphAsync(let token):
|
||||
return ("statsGraphAsync", [("token", token)])
|
||||
case .statsGraphError(let error):
|
||||
return ("statsGraphError", [("error", error)])
|
||||
case .statsGraph(let json):
|
||||
return ("statsGraph", [("json", json)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraphAsync(token: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraphError(error: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: Api.DataJSON?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraph(json: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PageTableCell: TypeConstructorDescription {
|
||||
case pageTableCell(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?)
|
||||
@ -13662,6 +13822,44 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsPercentValue: TypeConstructorDescription {
|
||||
case statsPercentValue(part: Double, total: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsPercentValue(let part, let total):
|
||||
if boxed {
|
||||
buffer.appendInt32(-875679776)
|
||||
}
|
||||
serializeDouble(part, buffer: buffer, boxed: false)
|
||||
serializeDouble(total, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsPercentValue(let part, let total):
|
||||
return ("statsPercentValue", [("part", part), ("total", total)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? {
|
||||
var _1: Double?
|
||||
_1 = reader.readDouble()
|
||||
var _2: Double?
|
||||
_2 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChannelLocation: TypeConstructorDescription {
|
||||
case channelLocationEmpty
|
||||
@ -14434,6 +14632,44 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsDateRangeDays: TypeConstructorDescription {
|
||||
case statsDateRangeDays(minDate: Int32, maxDate: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsDateRangeDays(let minDate, let maxDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1237848657)
|
||||
}
|
||||
serializeInt32(minDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(maxDate, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsDateRangeDays(let minDate, let maxDate):
|
||||
return ("statsDateRangeDays", [("minDate", minDate), ("maxDate", maxDate)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum MessageFwdAuthor: TypeConstructorDescription {
|
||||
case messageFwdAuthor(channelId: Int32)
|
||||
@ -15098,6 +15334,44 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsAbsValueAndPrev: TypeConstructorDescription {
|
||||
case statsAbsValueAndPrev(current: Double, previous: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsAbsValueAndPrev(let current, let previous):
|
||||
if boxed {
|
||||
buffer.appendInt32(-884757282)
|
||||
}
|
||||
serializeDouble(current, buffer: buffer, boxed: false)
|
||||
serializeDouble(previous, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsAbsValueAndPrev(let current, let previous):
|
||||
return ("statsAbsValueAndPrev", [("current", current), ("previous", previous)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? {
|
||||
var _1: Double?
|
||||
_1 = reader.readDouble()
|
||||
var _2: Double?
|
||||
_2 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum MessageMedia: TypeConstructorDescription {
|
||||
case messageMediaEmpty
|
||||
@ -15112,6 +15386,7 @@ public extension Api {
|
||||
case messageMediaDocument(flags: Int32, document: Api.Document?, ttlSeconds: Int32?)
|
||||
case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int32)
|
||||
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
||||
case messageMediaDice(value: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -15209,6 +15484,12 @@ public extension Api {
|
||||
poll.serialize(buffer, true)
|
||||
results.serialize(buffer, true)
|
||||
break
|
||||
case .messageMediaDice(let value):
|
||||
if boxed {
|
||||
buffer.appendInt32(1670374507)
|
||||
}
|
||||
serializeInt32(value, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -15238,6 +15519,8 @@ public extension Api {
|
||||
return ("messageMediaContact", [("phoneNumber", phoneNumber), ("firstName", firstName), ("lastName", lastName), ("vcard", vcard), ("userId", userId)])
|
||||
case .messageMediaPoll(let poll, let results):
|
||||
return ("messageMediaPoll", [("poll", poll), ("results", results)])
|
||||
case .messageMediaDice(let value):
|
||||
return ("messageMediaDice", [("value", value)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -15443,6 +15726,17 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.MessageMedia.messageMediaDice(value: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PaymentSavedCredentials: TypeConstructorDescription {
|
||||
@ -16852,6 +17146,58 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum DialogFilter: TypeConstructorDescription {
|
||||
case dialogFilter(flags: Int32, id: Int32, title: String, includePeers: [Api.InputPeer])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers):
|
||||
if boxed {
|
||||
buffer.appendInt32(351868460)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(includePeers.count))
|
||||
for item in includePeers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers):
|
||||
return ("dialogFilter", [("flags", flags), ("id", id), ("title", title), ("includePeers", includePeers)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_dialogFilter(_ reader: BufferReader) -> DialogFilter? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, includePeers: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum InputPaymentCredentials: TypeConstructorDescription {
|
||||
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
|
||||
@ -20918,6 +21264,54 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsRowAbsValueAndPrev: TypeConstructorDescription {
|
||||
case statsRowAbsValueAndPrev(id: String, title: String, shortTitle: String, values: Api.StatsAbsValueAndPrev)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values):
|
||||
if boxed {
|
||||
buffer.appendInt32(-581804346)
|
||||
}
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
serializeString(shortTitle, buffer: buffer, boxed: false)
|
||||
values.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values):
|
||||
return ("statsRowAbsValueAndPrev", [("id", id), ("title", title), ("shortTitle", shortTitle), ("values", values)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsRowAbsValueAndPrev(_ reader: BufferReader) -> StatsRowAbsValueAndPrev? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.StatsRowAbsValueAndPrev.statsRowAbsValueAndPrev(id: _1!, title: _2!, shortTitle: _3!, values: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChatOnlines: TypeConstructorDescription {
|
||||
case chatOnlines(onlines: Int32)
|
||||
|
@ -537,6 +537,130 @@ public struct payments {
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
public struct stats {
|
||||
public enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, viewsBySource: [Api.StatsRowAbsValueAndPrev], newFollowersBySource: [Api.StatsRowAbsValueAndPrev], languages: [Api.StatsRowAbsValueAndPrev], growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph):
|
||||
if boxed {
|
||||
buffer.appendInt32(205195937)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.serialize(buffer, true)
|
||||
enabledNotifications.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(viewsBySource.count))
|
||||
for item in viewsBySource {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newFollowersBySource.count))
|
||||
for item in newFollowersBySource {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(languages.count))
|
||||
for item in languages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
growthGraph.serialize(buffer, true)
|
||||
followersGraph.serialize(buffer, true)
|
||||
muteGraph.serialize(buffer, true)
|
||||
topHoursGraph.serialize(buffer, true)
|
||||
interactionsGraph.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph):
|
||||
return ("broadcastStats", [("period", period), ("followers", followers), ("viewsPerPost", viewsPerPost), ("sharesPerPost", sharesPerPost), ("enabledNotifications", enabledNotifications), ("viewsBySource", viewsBySource), ("newFollowersBySource", newFollowersBySource), ("languages", languages), ("growthGraph", growthGraph), ("followersGraph", followersGraph), ("muteGraph", muteGraph), ("topHoursGraph", topHoursGraph), ("interactionsGraph", interactionsGraph)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? {
|
||||
var _1: Api.StatsDateRangeDays?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays
|
||||
}
|
||||
var _2: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _3: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _5: Api.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _6: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _7: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _8: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _9: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _10: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_10 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _11: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _12: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _13: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
let _c12 = _12 != nil
|
||||
let _c13 = _13 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, viewsBySource: _6!, newFollowersBySource: _7!, languages: _8!, growthGraph: _9!, followersGraph: _10!, muteGraph: _11!, topHoursGraph: _12!, interactionsGraph: _13!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
public struct auth {
|
||||
public enum LoginToken: TypeConstructorDescription {
|
||||
case loginToken(expires: Int32, token: Buffer)
|
||||
|
@ -3214,6 +3214,54 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilter]>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-241247891)
|
||||
|
||||
return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilter]? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: [Api.DialogFilter]?
|
||||
if let _ = reader.readInt32() {
|
||||
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self)
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(450142282)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", flags), ("id", id), ("filter", filter)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-983318044)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(order.count))
|
||||
for item in order {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", order)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
@ -3900,6 +3948,36 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct stats {
|
||||
public static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.BroadcastStats>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1421720550)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
channel.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", flags), ("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.stats.BroadcastStats?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func loadAsyncGraph(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.StatsGraph>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1749505346)
|
||||
serializeString(token, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct auth {
|
||||
public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.CheckedPhone>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -152,7 +152,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaWebpageAttribute.self, f: { TelegramMediaWebpageAttribute(decoder: $0) })
|
||||
declareEncodable(CachedPollOptionResult.self, f: { CachedPollOptionResult(decoder: $0) })
|
||||
//declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) })
|
||||
declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
@ -140,7 +140,7 @@ public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/*extension ChatListFilter {
|
||||
extension ChatListFilter {
|
||||
init(apiFilter: Api.DialogFilter) {
|
||||
switch apiFilter {
|
||||
case let .dialogFilter(flags, id, title, includePeers):
|
||||
@ -179,13 +179,13 @@ public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
})
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public enum RequestUpdateChatListFilterError {
|
||||
case generic
|
||||
}
|
||||
|
||||
/*public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
return account.postbox.transaction { transaction -> Api.DialogFilter? in
|
||||
return filter?.apiFilter(transaction: transaction)
|
||||
}
|
||||
@ -270,6 +270,8 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
}
|
||||
|
||||
func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
||||
return .complete()
|
||||
|
||||
return requestChatListFilters(postbox: postbox, network: network)
|
||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
||||
return .complete()
|
||||
@ -376,4 +378,4 @@ public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @es
|
||||
return result ?? .default
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import SyncCore
|
||||
|
||||
private final class ManagedChatListHolesState {
|
||||
private var holeDisposables: [ChatListHolesEntry: Disposable] = [:]
|
||||
@ -53,15 +54,17 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee
|
||||
return Signal { _ in
|
||||
let state = Atomic(value: ManagedChatListHolesState())
|
||||
|
||||
let topRootHoleKey = PostboxViewKey.allChatListHoles(.root)
|
||||
let topRootHole = postbox.combinedView(keys: [topRootHoleKey])
|
||||
let topRootHoleKey: PostboxViewKey = .allChatListHoles(.root)
|
||||
let filtersKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.chatListFilters]))
|
||||
let combinedView = postbox.combinedView(keys: [topRootHoleKey, filtersKey])
|
||||
|
||||
let disposable = combineLatest(postbox.chatListHolesView(), topRootHole).start(next: { view, topRootHoleView in
|
||||
let disposable = combineLatest(postbox.chatListHolesView(), combinedView).start(next: { view, combinedView in
|
||||
var additionalLatestHole: ChatListHole?
|
||||
if let topRootHole = topRootHoleView.views[topRootHoleKey] as? AllChatListHolesView {
|
||||
#if os(macOS)
|
||||
additionalLatestHole = topRootHole.latestHole
|
||||
#endif
|
||||
|
||||
if let preferencesView = combinedView.views[filtersKey] as? PreferencesView, let filtersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState, !filtersState.filters.isEmpty {
|
||||
if let topRootHole = combinedView.views[topRootHoleKey] as? AllChatListHolesView {
|
||||
additionalLatestHole = topRootHole.latestHole
|
||||
}
|
||||
}
|
||||
|
||||
let (removed, added, addedAdditionalLatestHole) = state.with { state in
|
||||
|
@ -277,71 +277,73 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
|
||||
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId:PeerId) -> (Media?, Int32?) {
|
||||
if let media = media {
|
||||
switch media {
|
||||
case let .messageMediaPhoto(_, photo, ttlSeconds):
|
||||
if let photo = photo {
|
||||
if let mediaImage = telegramMediaImageFromApiPhoto(photo) {
|
||||
return (mediaImage, ttlSeconds)
|
||||
}
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .image), nil)
|
||||
case let .messageMediaPhoto(_, photo, ttlSeconds):
|
||||
if let photo = photo {
|
||||
if let mediaImage = telegramMediaImageFromApiPhoto(photo) {
|
||||
return (mediaImage, ttlSeconds)
|
||||
}
|
||||
case let .messageMediaContact(phoneNumber, firstName, lastName, vcard, userId):
|
||||
let contactPeerId: PeerId? = userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
||||
return (mediaContact, nil)
|
||||
case let .messageMediaGeo(geo):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaGeoLive(geo, period):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaDocument(_, document, ttlSeconds):
|
||||
if let document = document {
|
||||
if let mediaFile = telegramMediaFileFromApiDocument(document) {
|
||||
return (mediaFile, ttlSeconds)
|
||||
}
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .file), nil)
|
||||
}
|
||||
case let .messageMediaWebPage(webpage):
|
||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
||||
return (mediaWebpage, nil)
|
||||
}
|
||||
case .messageMediaUnsupported:
|
||||
return (TelegramMediaUnsupported(), nil)
|
||||
case .messageMediaEmpty:
|
||||
break
|
||||
case let .messageMediaGame(game):
|
||||
return (TelegramMediaGame(apiGame: game), nil)
|
||||
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
|
||||
var parsedFlags = TelegramMediaInvoiceFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
parsedFlags.insert(.isTest)
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .image), nil)
|
||||
}
|
||||
case let .messageMediaContact(phoneNumber, firstName, lastName, vcard, userId):
|
||||
let contactPeerId: PeerId? = userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
||||
return (mediaContact, nil)
|
||||
case let .messageMediaGeo(geo):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaGeoLive(geo, period):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period)
|
||||
return (mediaMap, nil)
|
||||
case let .messageMediaDocument(_, document, ttlSeconds):
|
||||
if let document = document {
|
||||
if let mediaFile = telegramMediaFileFromApiDocument(document) {
|
||||
return (mediaFile, ttlSeconds)
|
||||
}
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .file), nil)
|
||||
}
|
||||
case let .messageMediaWebPage(webpage):
|
||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
||||
return (mediaWebpage, nil)
|
||||
}
|
||||
case .messageMediaUnsupported:
|
||||
return (TelegramMediaUnsupported(), nil)
|
||||
case .messageMediaEmpty:
|
||||
break
|
||||
case let .messageMediaGame(game):
|
||||
return (TelegramMediaGame(apiGame: game), nil)
|
||||
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
|
||||
var parsedFlags = TelegramMediaInvoiceFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
parsedFlags.insert(.isTest)
|
||||
}
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
parsedFlags.insert(.shippingAddressRequested)
|
||||
}
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags), nil)
|
||||
case let .messageMediaPoll(poll, results):
|
||||
switch poll {
|
||||
case let .poll(id, flags, question, answers):
|
||||
let publicity: TelegramMediaPollPublicity
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
parsedFlags.insert(.shippingAddressRequested)
|
||||
publicity = .public
|
||||
} else {
|
||||
publicity = .anonymous
|
||||
}
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags), nil)
|
||||
case let .messageMediaPoll(poll, results):
|
||||
switch poll {
|
||||
case let .poll(id, flags, question, answers):
|
||||
let publicity: TelegramMediaPollPublicity
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
publicity = .public
|
||||
} else {
|
||||
publicity = .anonymous
|
||||
}
|
||||
let kind: TelegramMediaPollKind
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
kind = .quiz
|
||||
} else {
|
||||
kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0)
|
||||
}
|
||||
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0), nil)
|
||||
let kind: TelegramMediaPollKind
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
kind = .quiz
|
||||
} else {
|
||||
kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0)
|
||||
}
|
||||
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0), nil)
|
||||
}
|
||||
case .messageMediaDice:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,7 +319,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
}
|
||||
|
||||
switch fullChat {
|
||||
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, pts):
|
||||
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, _, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, _, pts):
|
||||
var channelFlags = CachedChannelFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
channelFlags.insert(.canDisplayParticipants)
|
||||
|
@ -54,6 +54,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
||||
declareEncodable(WebBrowserSettings.self, f: { WebBrowserSettings(decoder: $0) })
|
||||
declareEncodable(IntentsSettings.self, f: { IntentsSettings(decoder: $0) })
|
||||
declareEncodable(CachedGeocode.self, f: { CachedGeocode(decoder: $0) })
|
||||
declareEncodable(ChatListFilterSettings.self, f: { ChatListFilterSettings(decoder: $0) })
|
||||
return
|
||||
}()
|
||||
|
||||
|
@ -65,6 +65,8 @@ struct VideoConversionConfiguration {
|
||||
}
|
||||
|
||||
static func with(appConfiguration: AppConfiguration) -> VideoConversionConfiguration {
|
||||
return VideoConversionConfiguration(remuxToFMp4: true)
|
||||
|
||||
if let data = appConfiguration.data, let conversion = data["video_conversion"] as? [String: Any] {
|
||||
let remuxToFMp4 = conversion["remux_fmp4"] as? Bool ?? VideoConversionConfiguration.defaultValue.remuxToFMp4
|
||||
return VideoConversionConfiguration(remuxToFMp4: remuxToFMp4)
|
||||
|
@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
public struct ChatListFilterSettings: Equatable, PreferencesEntry {
|
||||
public var displayTabs: Bool
|
||||
|
||||
public static var `default`: ChatListFilterSettings {
|
||||
return ChatListFilterSettings(displayTabs: true)
|
||||
}
|
||||
|
||||
public init(displayTabs: Bool) {
|
||||
self.displayTabs = displayTabs
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.displayTabs = decoder.decodeInt32ForKey("displayTabs", orElse: 1) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.displayTabs ? 1 : 0, forKey: "displayTabs")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? ChatListFilterSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChatListFilterSettings(transaction: Transaction, _ f: @escaping (ChatListFilterSettings) -> ChatListFilterSettings) {
|
||||
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings, { entry in
|
||||
let currentSettings: ChatListFilterSettings
|
||||
if let entry = entry as? ChatListFilterSettings {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = .default
|
||||
}
|
||||
return f(currentSettings)
|
||||
})
|
||||
}
|
@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FFMpegAVIOContext : NSObject
|
||||
|
||||
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (*)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek;
|
||||
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek;
|
||||
|
||||
- (void *)impl;
|
||||
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
@implementation FFMpegAVIOContext
|
||||
|
||||
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (*)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek {
|
||||
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
void *avIoBuffer = av_malloc(bufferSize);
|
||||
_impl = avio_alloc_context(avIoBuffer, bufferSize, 0, opaqueContext, readPacket, nil, seek);
|
||||
_impl = avio_alloc_context(avIoBuffer, bufferSize, 0, opaqueContext, readPacket, writePacket, seek);
|
||||
_impl->direct = 1;
|
||||
if (_impl == nil) {
|
||||
av_free(avIoBuffer);
|
||||
|
@ -1,11 +1,64 @@
|
||||
#import "FFMpegRemuxer.h"
|
||||
|
||||
#import "FFMpegAVIOContext.h"
|
||||
|
||||
#include "libavutil/timestamp.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
|
||||
#define MOV_TIMESCALE 1000
|
||||
|
||||
@interface FFMpegRemuxerContext : NSObject {
|
||||
@public
|
||||
int _fd;
|
||||
int64_t _offset;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FFMpegRemuxerContext
|
||||
|
||||
- (instancetype)initWithFileName:(NSString *)fileName {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_fd = open(fileName.UTF8String, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_fd > 0) {
|
||||
close(_fd);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static int readPacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
||||
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
||||
context->_offset += length;
|
||||
printf("read %lld bytes (offset is now %lld)\n", length, context->_offset);
|
||||
return read(context->_fd, buffer, length);
|
||||
}
|
||||
|
||||
static int writePacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
||||
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
||||
context->_offset += length;
|
||||
printf("write %lld bytes (offset is now %lld)\n", length, context->_offset);
|
||||
return write(context->_fd, buffer, length);
|
||||
}
|
||||
|
||||
static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
|
||||
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
||||
printf("seek to %lld\n", offset);
|
||||
if (whence == FFMPEG_AVSEEK_SIZE) {
|
||||
return 0;
|
||||
} else {
|
||||
context->_offset = offset;
|
||||
return lseek(context->_fd, offset, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation FFMpegRemuxer
|
||||
|
||||
+ (bool)remux:(NSString * _Nonnull)path to:(NSString * _Nonnull)outPath {
|
||||
@ -21,6 +74,9 @@
|
||||
in_filename = [path UTF8String];
|
||||
out_filename = [outPath UTF8String];
|
||||
|
||||
//FFMpegRemuxerContext *outputContext = [[FFMpegRemuxerContext alloc] initWithFileName:outPath];
|
||||
//FFMpegAVIOContext *outputIoContext = [[FFMpegAVIOContext alloc] initWithBufferSize:1024 opaqueContext:(__bridge void *)outputContext readPacket:&readPacketImpl writePacket:&writePacketImpl seek:&seekImpl];
|
||||
|
||||
if ((ret = avformat_open_input(&input_format_context, in_filename, av_find_input_format("mov"), NULL)) < 0) {
|
||||
fprintf(stderr, "Could not open input file '%s'", in_filename);
|
||||
goto end;
|
||||
@ -31,6 +87,11 @@
|
||||
}
|
||||
|
||||
avformat_alloc_output_context2(&output_format_context, NULL, NULL, out_filename);
|
||||
//output_format_context = avformat_alloc_context();
|
||||
//output_format_context->pb = outputIoContext.impl;
|
||||
//output_format_context->flags |= AVFMT_FLAG_CUSTOM_IO;
|
||||
//output_format_context->oformat = av_guess_format("mp4", NULL, NULL);
|
||||
|
||||
if (!output_format_context) {
|
||||
fprintf(stderr, "Could not create output context\n");
|
||||
ret = AVERROR_UNKNOWN;
|
||||
@ -103,7 +164,7 @@
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API/Transcoding_assets_for_MSE
|
||||
av_dict_set(&opts, "movflags", "dash+faststart+global_sidx+skip_trailer", 0);
|
||||
if (maxTrackLength > 0) {
|
||||
av_dict_set_int(&opts, "custom_maxTrackLength", maxTrackLength, 0);
|
||||
//av_dict_set_int(&opts, "custom_maxTrackLength", maxTrackLength, 0);
|
||||
}
|
||||
}
|
||||
// https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga18b7b10bb5b94c4842de18166bc677cb
|
||||
@ -144,8 +205,9 @@
|
||||
end:
|
||||
avformat_close_input(&input_format_context);
|
||||
/* close output */
|
||||
if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE))
|
||||
if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE)) {
|
||||
avio_closep(&output_format_context->pb);
|
||||
}
|
||||
avformat_free_context(output_format_context);
|
||||
av_freep(&streams_list);
|
||||
if (ret < 0 && ret != AVERROR_EOF) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user