mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Support updated API
This commit is contained in:
parent
f8ebd4aa2f
commit
f4545aaeeb
454
submodules/ChatListUI/Sources/ChatListContainerItemNode.swift
Normal file
454
submodules/ChatListUI/Sources/ChatListContainerItemNode.swift
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import ChatListHeaderComponent
|
||||||
|
import ActionPanelComponent
|
||||||
|
import ChatFolderLinkPreviewScreen
|
||||||
|
|
||||||
|
final class ChatListContainerItemNode: ASDisplayNode {
|
||||||
|
private final class TopPanelItem {
|
||||||
|
let view = ComponentView<Empty>()
|
||||||
|
var size: CGSize?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
|
private weak var controller: ChatListControllerImpl?
|
||||||
|
private let location: ChatListControllerLocation
|
||||||
|
private let animationCache: AnimationCache
|
||||||
|
private let animationRenderer: MultiAnimationRenderer
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
private let becameEmpty: (ChatListFilter?) -> Void
|
||||||
|
private let emptyAction: (ChatListFilter?) -> Void
|
||||||
|
private let secondaryEmptyAction: () -> Void
|
||||||
|
private let openArchiveSettings: () -> Void
|
||||||
|
private let isInlineMode: Bool
|
||||||
|
|
||||||
|
private var floatingHeaderOffset: CGFloat?
|
||||||
|
|
||||||
|
private(set) var emptyNode: ChatListEmptyNode?
|
||||||
|
var emptyShimmerEffectNode: ChatListShimmerNode?
|
||||||
|
private var shimmerNodeOffset: CGFloat = 0.0
|
||||||
|
let listNode: ChatListNode
|
||||||
|
|
||||||
|
private var topPanel: TopPanelItem?
|
||||||
|
|
||||||
|
private var pollFilterUpdatesDisposable: Disposable?
|
||||||
|
private var chatFilterUpdatesDisposable: Disposable?
|
||||||
|
private var peerDataDisposable: Disposable?
|
||||||
|
|
||||||
|
private var chatFolderUpdates: ChatFolderUpdates?
|
||||||
|
|
||||||
|
private var canReportPeer: Bool = false
|
||||||
|
|
||||||
|
private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat)?
|
||||||
|
private var scrollingOffset: (navigationHeight: CGFloat, offset: CGFloat)?
|
||||||
|
|
||||||
|
init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, openArchiveSettings: @escaping () -> Void, autoSetReady: Bool) {
|
||||||
|
self.context = context
|
||||||
|
self.controller = controller
|
||||||
|
self.location = location
|
||||||
|
self.animationCache = animationCache
|
||||||
|
self.animationRenderer = animationRenderer
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.becameEmpty = becameEmpty
|
||||||
|
self.emptyAction = emptyAction
|
||||||
|
self.secondaryEmptyAction = secondaryEmptyAction
|
||||||
|
self.openArchiveSettings = openArchiveSettings
|
||||||
|
self.isInlineMode = isInlineMode
|
||||||
|
|
||||||
|
self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: chatListMode, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode, autoSetReady: autoSetReady)
|
||||||
|
|
||||||
|
if let controller, case .chatList(groupId: .root) = controller.location {
|
||||||
|
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.listNode)
|
||||||
|
|
||||||
|
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var needsShimmerNode = false
|
||||||
|
var shimmerNodeOffset: CGFloat = 0.0
|
||||||
|
|
||||||
|
var needsEmptyNode = false
|
||||||
|
var hasOnlyArchive = false
|
||||||
|
var hasOnlyGeneralThread = false
|
||||||
|
var isLoading = false
|
||||||
|
|
||||||
|
switch isEmptyState {
|
||||||
|
case let .empty(isLoadingValue, hasArchiveInfo):
|
||||||
|
if hasArchiveInfo {
|
||||||
|
shimmerNodeOffset = 253.0
|
||||||
|
}
|
||||||
|
if isLoadingValue {
|
||||||
|
needsShimmerNode = true
|
||||||
|
needsEmptyNode = false
|
||||||
|
isLoading = isLoadingValue
|
||||||
|
} else {
|
||||||
|
needsEmptyNode = true
|
||||||
|
}
|
||||||
|
if !isLoadingValue {
|
||||||
|
strongSelf.becameEmpty(filter)
|
||||||
|
}
|
||||||
|
case let .notEmpty(_, onlyHasArchiveValue, onlyGeneralThreadValue):
|
||||||
|
needsEmptyNode = onlyHasArchiveValue || onlyGeneralThreadValue
|
||||||
|
hasOnlyArchive = onlyHasArchiveValue
|
||||||
|
hasOnlyGeneralThread = onlyGeneralThreadValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsEmptyNode {
|
||||||
|
if let currentNode = strongSelf.emptyNode {
|
||||||
|
currentNode.updateIsLoading(isLoading)
|
||||||
|
} else {
|
||||||
|
let subject: ChatListEmptyNode.Subject
|
||||||
|
if let filter = filter {
|
||||||
|
var showEdit = true
|
||||||
|
if case let .filter(_, _, _, data) = filter {
|
||||||
|
if data.excludeRead && data.includePeers.peers.isEmpty && data.includePeers.pinnedPeers.isEmpty {
|
||||||
|
showEdit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subject = .filter(showEdit: showEdit)
|
||||||
|
} else {
|
||||||
|
if case .forum = location {
|
||||||
|
subject = .forum(hasGeneral: hasOnlyGeneralThread)
|
||||||
|
} else {
|
||||||
|
if case .chatList(groupId: .archive) = location {
|
||||||
|
subject = .archive
|
||||||
|
} else {
|
||||||
|
subject = .chats(hasArchive: hasOnlyArchive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emptyNode = ChatListEmptyNode(context: context, subject: subject, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
|
||||||
|
self?.emptyAction(filter)
|
||||||
|
}, secondaryAction: {
|
||||||
|
self?.secondaryEmptyAction()
|
||||||
|
}, openArchiveSettings: {
|
||||||
|
self?.openArchiveSettings()
|
||||||
|
})
|
||||||
|
strongSelf.emptyNode = emptyNode
|
||||||
|
strongSelf.listNode.addSubnode(emptyNode)
|
||||||
|
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout {
|
||||||
|
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||||
|
emptyNode.frame = emptyNodeFrame
|
||||||
|
emptyNode.updateLayout(size: size, insets: insets, transition: .immediate)
|
||||||
|
|
||||||
|
if let scrollingOffset = strongSelf.scrollingOffset {
|
||||||
|
emptyNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emptyNode.alpha = 0.0
|
||||||
|
transition.updateAlpha(node: emptyNode, alpha: 1.0)
|
||||||
|
}
|
||||||
|
} else if let emptyNode = strongSelf.emptyNode {
|
||||||
|
strongSelf.emptyNode = nil
|
||||||
|
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
|
||||||
|
emptyNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if needsShimmerNode {
|
||||||
|
strongSelf.shimmerNodeOffset = shimmerNodeOffset
|
||||||
|
if strongSelf.emptyShimmerEffectNode == nil {
|
||||||
|
let emptyShimmerEffectNode = ChatListShimmerNode()
|
||||||
|
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
|
||||||
|
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
|
||||||
|
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
|
||||||
|
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||||
|
strongSelf.emptyShimmerEffectNode = nil
|
||||||
|
let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut)
|
||||||
|
emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in
|
||||||
|
emptyShimmerEffectNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
strongSelf.listNode.alpha = 0.0
|
||||||
|
emptyNodeTransition.updateAlpha(node: strongSelf.listNode, alpha: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.floatingHeaderOffset = offset
|
||||||
|
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
||||||
|
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
|
||||||
|
}
|
||||||
|
strongSelf.layoutAdditionalPanels(transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let filter, case let .filter(id, _, _, data) = filter, data.isShared {
|
||||||
|
self.pollFilterUpdatesDisposable = self.context.engine.peers.pollChatFolderUpdates(folderId: id).start()
|
||||||
|
self.chatFilterUpdatesDisposable = (self.context.engine.peers.subscribedChatFolderUpdates(folderId: id)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var update = false
|
||||||
|
if let result, result.availableChatsToJoin != 0 {
|
||||||
|
if self.chatFolderUpdates?.availableChatsToJoin != result.availableChatsToJoin {
|
||||||
|
update = true
|
||||||
|
}
|
||||||
|
self.chatFolderUpdates = result
|
||||||
|
} else {
|
||||||
|
if self.chatFolderUpdates != nil {
|
||||||
|
self.chatFolderUpdates = nil
|
||||||
|
update = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if update {
|
||||||
|
if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout {
|
||||||
|
self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .forum(peerId) = location {
|
||||||
|
self.peerDataDisposable = (context.engine.data.subscribe(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.StatusSettings(id: peerId)
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] statusSettings in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var canReportPeer = false
|
||||||
|
if let statusSettings, statusSettings.flags.contains(.canReport) {
|
||||||
|
canReportPeer = true
|
||||||
|
}
|
||||||
|
if self.canReportPeer != canReportPeer {
|
||||||
|
self.canReportPeer = canReportPeer
|
||||||
|
if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout {
|
||||||
|
self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.pollFilterUpdatesDisposable?.dispose()
|
||||||
|
self.chatFilterUpdatesDisposable?.dispose()
|
||||||
|
self.peerDataDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, isInlineMode: self.isInlineMode, presentationData: self.presentationData, transition: .immediate)
|
||||||
|
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func layoutAdditionalPanels(transition: ContainedViewLayoutTransition) {
|
||||||
|
guard let (size, insets, visualNavigationHeight, _, _, _, _) = self.validLayout, let offset = self.floatingHeaderOffset else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = size
|
||||||
|
let _ = insets
|
||||||
|
|
||||||
|
if let topPanel = self.topPanel, let topPanelSize = topPanel.size {
|
||||||
|
let minY: CGFloat = visualNavigationHeight - 44.0 + topPanelSize.height
|
||||||
|
|
||||||
|
if let topPanelView = topPanel.view.view {
|
||||||
|
var animateIn = false
|
||||||
|
var topPanelTransition = transition
|
||||||
|
if topPanelView.bounds.isEmpty {
|
||||||
|
topPanelTransition = .immediate
|
||||||
|
animateIn = true
|
||||||
|
}
|
||||||
|
topPanelTransition.updateFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: max(minY, offset - topPanelSize.height)), size: topPanelSize))
|
||||||
|
if animateIn {
|
||||||
|
transition.animatePositionAdditive(layer: topPanelView.layer, offset: CGPoint(x: 0.0, y: -topPanelView.bounds.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||||
|
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
|
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset)
|
||||||
|
|
||||||
|
var listInsets = insets
|
||||||
|
var additionalTopInset: CGFloat = 0.0
|
||||||
|
|
||||||
|
if let chatFolderUpdates = self.chatFolderUpdates {
|
||||||
|
let topPanel: TopPanelItem
|
||||||
|
var topPanelTransition = Transition(transition)
|
||||||
|
if let current = self.topPanel {
|
||||||
|
topPanel = current
|
||||||
|
} else {
|
||||||
|
topPanelTransition = .immediate
|
||||||
|
topPanel = TopPanelItem()
|
||||||
|
self.topPanel = topPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: String = self.presentationData.strings.ChatList_PanelNewChatsAvailable(Int32(chatFolderUpdates.availableChatsToJoin))
|
||||||
|
|
||||||
|
let topPanelHeight: CGFloat = 44.0
|
||||||
|
|
||||||
|
let _ = topPanel.view.update(
|
||||||
|
transition: topPanelTransition,
|
||||||
|
component: AnyComponent(ActionPanelComponent(
|
||||||
|
theme: self.presentationData.theme,
|
||||||
|
title: title,
|
||||||
|
color: .accent,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listNode.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(chatFolderUpdates), contents: chatFolderUpdates.chatFolderLinkContents))
|
||||||
|
},
|
||||||
|
dismissAction: { [weak self] in
|
||||||
|
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = self.context.engine.peers.hideChatFolderUpdates(folderId: chatFolderUpdates.folderId).start()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: topPanelHeight)
|
||||||
|
)
|
||||||
|
if let topPanelView = topPanel.view.view {
|
||||||
|
if topPanelView.superview == nil {
|
||||||
|
self.view.addSubview(topPanelView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topPanel.size = CGSize(width: size.width, height: topPanelHeight)
|
||||||
|
listInsets.top += topPanelHeight
|
||||||
|
additionalTopInset += topPanelHeight
|
||||||
|
} else if self.canReportPeer {
|
||||||
|
let topPanel: TopPanelItem
|
||||||
|
var topPanelTransition = Transition(transition)
|
||||||
|
if let current = self.topPanel {
|
||||||
|
topPanel = current
|
||||||
|
} else {
|
||||||
|
topPanelTransition = .immediate
|
||||||
|
topPanel = TopPanelItem()
|
||||||
|
self.topPanel = topPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: String = self.presentationData.strings.Conversation_ReportSpamAndLeave
|
||||||
|
|
||||||
|
let topPanelHeight: CGFloat = 44.0
|
||||||
|
|
||||||
|
let _ = topPanel.view.update(
|
||||||
|
transition: topPanelTransition,
|
||||||
|
component: AnyComponent(ActionPanelComponent(
|
||||||
|
theme: self.presentationData.theme,
|
||||||
|
title: title,
|
||||||
|
color: .destructive,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, case let .forum(peerId) = self.location else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||||
|
actionSheet.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: self.presentationData.strings.Conversation_ReportSpamGroupConfirmation),
|
||||||
|
ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ReportSpamAndLeave, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
|
if let self {
|
||||||
|
self.controller?.setInlineChatList(location: nil)
|
||||||
|
let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: true).start()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
self.listNode.present?(actionSheet)
|
||||||
|
},
|
||||||
|
dismissAction: { [weak self] in
|
||||||
|
guard let self, case let .forum(peerId) = self.location else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = self.context.engine.peers.dismissPeerStatusOptions(peerId: peerId).start()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: topPanelHeight)
|
||||||
|
)
|
||||||
|
if let topPanelView = topPanel.view.view {
|
||||||
|
if topPanelView.superview == nil {
|
||||||
|
self.view.addSubview(topPanelView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topPanel.size = CGSize(width: size.width, height: topPanelHeight)
|
||||||
|
listInsets.top += topPanelHeight
|
||||||
|
additionalTopInset += topPanelHeight
|
||||||
|
} else {
|
||||||
|
if let topPanel = self.topPanel {
|
||||||
|
self.topPanel = nil
|
||||||
|
if let topPanelView = topPanel.view.view {
|
||||||
|
transition.updatePosition(layer: topPanelView.layer, position: CGPoint(x: topPanelView.layer.position.x, y: topPanelView.layer.position.y - topPanelView.layer.bounds.height), completion: { [weak topPanelView] _ in
|
||||||
|
topPanelView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: listInsets, duration: duration, curve: curve)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight + additionalTopInset, originalTopInset: originalNavigationHeight + additionalTopInset, storiesInset: storiesInset, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction)
|
||||||
|
|
||||||
|
if let emptyNode = self.emptyNode {
|
||||||
|
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||||
|
transition.updateFrame(node: emptyNode, frame: emptyNodeFrame)
|
||||||
|
emptyNode.updateLayout(size: emptyNodeFrame.size, insets: listInsets, transition: transition)
|
||||||
|
|
||||||
|
if let scrollingOffset = self.scrollingOffset {
|
||||||
|
emptyNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layoutAdditionalPanels(transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.scrollingOffset = (navigationHeight, offset)
|
||||||
|
|
||||||
|
if let emptyNode = self.emptyNode {
|
||||||
|
emptyNode.updateScrollingOffset(navigationHeight: navigationHeight, offset: offset, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,724 +44,6 @@ public enum ChatListContainerNodeFilter: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ShimmerEffectNode: ASDisplayNode {
|
|
||||||
private var currentBackgroundColor: UIColor?
|
|
||||||
private var currentForegroundColor: UIColor?
|
|
||||||
private let imageNodeContainer: ASDisplayNode
|
|
||||||
private let imageNode: ASImageNode
|
|
||||||
|
|
||||||
private var absoluteLocation: (CGRect, CGSize)?
|
|
||||||
private var isCurrentlyInHierarchy = false
|
|
||||||
private var shouldBeAnimating = false
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.imageNodeContainer = ASDisplayNode()
|
|
||||||
self.imageNodeContainer.isLayerBacked = true
|
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
|
||||||
self.imageNode.isLayerBacked = true
|
|
||||||
self.imageNode.displaysAsynchronously = false
|
|
||||||
self.imageNode.displayWithoutProcessing = true
|
|
||||||
self.imageNode.contentMode = .scaleToFill
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.isLayerBacked = true
|
|
||||||
self.clipsToBounds = true
|
|
||||||
|
|
||||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
|
||||||
self.addSubnode(self.imageNodeContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didEnterHierarchy() {
|
|
||||||
super.didEnterHierarchy()
|
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = true
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
|
||||||
super.didExitHierarchy()
|
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = false
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
|
||||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.currentBackgroundColor = backgroundColor
|
|
||||||
self.currentForegroundColor = foregroundColor
|
|
||||||
|
|
||||||
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
|
||||||
context.setFillColor(backgroundColor.cgColor)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
||||||
let peakColor = foregroundColor.cgColor
|
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
||||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
||||||
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
||||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
|
||||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
|
||||||
self.absoluteLocation = (rect, containerSize)
|
|
||||||
|
|
||||||
if sizeUpdated {
|
|
||||||
if self.shouldBeAnimating {
|
|
||||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
||||||
self.addImageAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if frameUpdated {
|
|
||||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateAnimation() {
|
|
||||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
|
||||||
if shouldBeAnimating != self.shouldBeAnimating {
|
|
||||||
self.shouldBeAnimating = shouldBeAnimating
|
|
||||||
if shouldBeAnimating {
|
|
||||||
self.addImageAnimation()
|
|
||||||
} else {
|
|
||||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addImageAnimation() {
|
|
||||||
guard let containerSize = self.absoluteLocation?.1 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let gradientHeight: CGFloat = 250.0
|
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
|
||||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
||||||
animation.repeatCount = Float.infinity
|
|
||||||
animation.beginTime = 1.0
|
|
||||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ChatListShimmerNode: ASDisplayNode {
|
|
||||||
private let backgroundColorNode: ASDisplayNode
|
|
||||||
private let effectNode: ShimmerEffectNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
private var currentParams: (size: CGSize, presentationData: PresentationData)?
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.backgroundColorNode = ASDisplayNode()
|
|
||||||
self.effectNode = ShimmerEffectNode()
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.addSubnode(self.backgroundColorNode)
|
|
||||||
self.addSubnode(self.effectNode)
|
|
||||||
self.addSubnode(self.maskNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, isInlineMode: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
|
||||||
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
|
|
||||||
self.currentParams = (size, presentationData)
|
|
||||||
|
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
|
||||||
|
|
||||||
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
|
|
||||||
let timestamp1: Int32 = 100000
|
|
||||||
let peers: [EnginePeer.Id: EnginePeer] = [:]
|
|
||||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
|
||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
|
||||||
gesture?.cancel()
|
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in })
|
|
||||||
interaction.isInlineMode = isInlineMode
|
|
||||||
|
|
||||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
|
||||||
let message = EngineMessage(
|
|
||||||
stableId: 0,
|
|
||||||
stableVersion: 0,
|
|
||||||
id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0),
|
|
||||||
globallyUniqueId: nil,
|
|
||||||
groupingKey: nil,
|
|
||||||
groupInfo: nil,
|
|
||||||
threadId: nil,
|
|
||||||
timestamp: timestamp1,
|
|
||||||
flags: [],
|
|
||||||
tags: [],
|
|
||||||
globalTags: [],
|
|
||||||
localTags: [],
|
|
||||||
forwardInfo: nil,
|
|
||||||
author: peer1,
|
|
||||||
text: "Text",
|
|
||||||
attributes: [],
|
|
||||||
media: [],
|
|
||||||
peers: peers,
|
|
||||||
associatedMessages: [:],
|
|
||||||
associatedMessageIds: [],
|
|
||||||
associatedMedia: [:],
|
|
||||||
associatedThreadInfo: nil,
|
|
||||||
associatedStories: [:]
|
|
||||||
)
|
|
||||||
let readState = EnginePeerReadCounters()
|
|
||||||
|
|
||||||
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(ChatListItemContent.PeerData(
|
|
||||||
messages: [message],
|
|
||||||
peer: EngineRenderedPeer(peer: peer1),
|
|
||||||
threadInfo: nil,
|
|
||||||
combinedReadState: readState,
|
|
||||||
isRemovedFromTotalUnreadCount: false,
|
|
||||||
presence: nil,
|
|
||||||
hasUnseenMentions: false,
|
|
||||||
hasUnseenReactions: false,
|
|
||||||
draftState: nil,
|
|
||||||
inputActivities: nil,
|
|
||||||
promoInfo: nil,
|
|
||||||
ignoreUnreadBadge: false,
|
|
||||||
displayAsMessage: false,
|
|
||||||
hasFailedMessages: false,
|
|
||||||
forumTopicData: nil,
|
|
||||||
topForumTopicItems: [],
|
|
||||||
autoremoveTimeout: nil,
|
|
||||||
storyState: nil
|
|
||||||
)), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemNodes: [ChatListItemNode] = []
|
|
||||||
for i in 0 ..< items.count {
|
|
||||||
items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
|
||||||
if let itemNode = node as? ChatListItemNode {
|
|
||||||
itemNodes.append(itemNode)
|
|
||||||
}
|
|
||||||
apply().1(ListViewItemApply(isOnScreen: true))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor
|
|
||||||
|
|
||||||
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
|
|
||||||
context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
var currentY: CGFloat = 0.0
|
|
||||||
let fakeLabelPlaceholderHeight: CGFloat = 8.0
|
|
||||||
|
|
||||||
func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) {
|
|
||||||
let startPoint = origin
|
|
||||||
let diameter = fakeLabelPlaceholderHeight
|
|
||||||
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
|
|
||||||
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
|
|
||||||
}
|
|
||||||
|
|
||||||
while currentY < size.height {
|
|
||||||
let sampleIndex = 0
|
|
||||||
let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height
|
|
||||||
|
|
||||||
context.setBlendMode(.copy)
|
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
|
||||||
|
|
||||||
if !isInlineMode {
|
|
||||||
if !itemNodes[sampleIndex].avatarNode.isHidden {
|
|
||||||
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.view.convert(itemNodes[sampleIndex].avatarNode.bounds, to: itemNodes[sampleIndex].view).offsetBy(dx: 0.0, dy: currentY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
|
||||||
if isInlineMode {
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 22.0, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0 - 22.0)
|
|
||||||
} else {
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
|
||||||
|
|
||||||
if isInlineMode {
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: textFrame.minX, y: titleFrame.minY + 2.0), size: CGSize(width: 16.0, height: 16.0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0)
|
|
||||||
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0)
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0)
|
|
||||||
|
|
||||||
let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
|
|
||||||
|
|
||||||
context.setBlendMode(.normal)
|
|
||||||
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
|
|
||||||
context.fill(itemNodes[sampleIndex].separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
|
|
||||||
|
|
||||||
currentY += itemHeight
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
|
||||||
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
|
|
||||||
}
|
|
||||||
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ChatListContainerItemNode: ASDisplayNode {
|
|
||||||
private final class TopPanelItem {
|
|
||||||
let view = ComponentView<Empty>()
|
|
||||||
var size: CGSize?
|
|
||||||
|
|
||||||
init() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let context: AccountContext
|
|
||||||
private weak var controller: ChatListControllerImpl?
|
|
||||||
private let location: ChatListControllerLocation
|
|
||||||
private let animationCache: AnimationCache
|
|
||||||
private let animationRenderer: MultiAnimationRenderer
|
|
||||||
private var presentationData: PresentationData
|
|
||||||
private let becameEmpty: (ChatListFilter?) -> Void
|
|
||||||
private let emptyAction: (ChatListFilter?) -> Void
|
|
||||||
private let secondaryEmptyAction: () -> Void
|
|
||||||
private let openArchiveSettings: () -> Void
|
|
||||||
private let isInlineMode: Bool
|
|
||||||
|
|
||||||
private var floatingHeaderOffset: CGFloat?
|
|
||||||
|
|
||||||
private(set) var emptyNode: ChatListEmptyNode?
|
|
||||||
var emptyShimmerEffectNode: ChatListShimmerNode?
|
|
||||||
private var shimmerNodeOffset: CGFloat = 0.0
|
|
||||||
let listNode: ChatListNode
|
|
||||||
|
|
||||||
private var topPanel: TopPanelItem?
|
|
||||||
|
|
||||||
private var pollFilterUpdatesDisposable: Disposable?
|
|
||||||
private var chatFilterUpdatesDisposable: Disposable?
|
|
||||||
private var peerDataDisposable: Disposable?
|
|
||||||
|
|
||||||
private var chatFolderUpdates: ChatFolderUpdates?
|
|
||||||
|
|
||||||
private var canReportPeer: Bool = false
|
|
||||||
|
|
||||||
private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat)?
|
|
||||||
private var scrollingOffset: (navigationHeight: CGFloat, offset: CGFloat)?
|
|
||||||
|
|
||||||
init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void, openArchiveSettings: @escaping () -> Void, autoSetReady: Bool) {
|
|
||||||
self.context = context
|
|
||||||
self.controller = controller
|
|
||||||
self.location = location
|
|
||||||
self.animationCache = animationCache
|
|
||||||
self.animationRenderer = animationRenderer
|
|
||||||
self.presentationData = presentationData
|
|
||||||
self.becameEmpty = becameEmpty
|
|
||||||
self.emptyAction = emptyAction
|
|
||||||
self.secondaryEmptyAction = secondaryEmptyAction
|
|
||||||
self.openArchiveSettings = openArchiveSettings
|
|
||||||
self.isInlineMode = isInlineMode
|
|
||||||
|
|
||||||
self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: chatListMode, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode, autoSetReady: autoSetReady)
|
|
||||||
|
|
||||||
if let controller, case .chatList(groupId: .root) = controller.location {
|
|
||||||
self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.listNode)
|
|
||||||
|
|
||||||
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var needsShimmerNode = false
|
|
||||||
var shimmerNodeOffset: CGFloat = 0.0
|
|
||||||
|
|
||||||
var needsEmptyNode = false
|
|
||||||
var hasOnlyArchive = false
|
|
||||||
var hasOnlyGeneralThread = false
|
|
||||||
var isLoading = false
|
|
||||||
|
|
||||||
switch isEmptyState {
|
|
||||||
case let .empty(isLoadingValue, hasArchiveInfo):
|
|
||||||
if hasArchiveInfo {
|
|
||||||
shimmerNodeOffset = 253.0
|
|
||||||
}
|
|
||||||
if isLoadingValue {
|
|
||||||
needsShimmerNode = true
|
|
||||||
needsEmptyNode = false
|
|
||||||
isLoading = isLoadingValue
|
|
||||||
} else {
|
|
||||||
needsEmptyNode = true
|
|
||||||
}
|
|
||||||
if !isLoadingValue {
|
|
||||||
strongSelf.becameEmpty(filter)
|
|
||||||
}
|
|
||||||
case let .notEmpty(_, onlyHasArchiveValue, onlyGeneralThreadValue):
|
|
||||||
needsEmptyNode = onlyHasArchiveValue || onlyGeneralThreadValue
|
|
||||||
hasOnlyArchive = onlyHasArchiveValue
|
|
||||||
hasOnlyGeneralThread = onlyGeneralThreadValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsEmptyNode {
|
|
||||||
if let currentNode = strongSelf.emptyNode {
|
|
||||||
currentNode.updateIsLoading(isLoading)
|
|
||||||
} else {
|
|
||||||
let subject: ChatListEmptyNode.Subject
|
|
||||||
if let filter = filter {
|
|
||||||
var showEdit = true
|
|
||||||
if case let .filter(_, _, _, data) = filter {
|
|
||||||
if data.excludeRead && data.includePeers.peers.isEmpty && data.includePeers.pinnedPeers.isEmpty {
|
|
||||||
showEdit = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subject = .filter(showEdit: showEdit)
|
|
||||||
} else {
|
|
||||||
if case .forum = location {
|
|
||||||
subject = .forum(hasGeneral: hasOnlyGeneralThread)
|
|
||||||
} else {
|
|
||||||
if case .chatList(groupId: .archive) = location {
|
|
||||||
subject = .archive
|
|
||||||
} else {
|
|
||||||
subject = .chats(hasArchive: hasOnlyArchive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let emptyNode = ChatListEmptyNode(context: context, subject: subject, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
|
|
||||||
self?.emptyAction(filter)
|
|
||||||
}, secondaryAction: {
|
|
||||||
self?.secondaryEmptyAction()
|
|
||||||
}, openArchiveSettings: {
|
|
||||||
self?.openArchiveSettings()
|
|
||||||
})
|
|
||||||
strongSelf.emptyNode = emptyNode
|
|
||||||
strongSelf.listNode.addSubnode(emptyNode)
|
|
||||||
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout {
|
|
||||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
|
||||||
emptyNode.frame = emptyNodeFrame
|
|
||||||
emptyNode.updateLayout(size: size, insets: insets, transition: .immediate)
|
|
||||||
|
|
||||||
if let scrollingOffset = strongSelf.scrollingOffset {
|
|
||||||
emptyNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emptyNode.alpha = 0.0
|
|
||||||
transition.updateAlpha(node: emptyNode, alpha: 1.0)
|
|
||||||
}
|
|
||||||
} else if let emptyNode = strongSelf.emptyNode {
|
|
||||||
strongSelf.emptyNode = nil
|
|
||||||
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
|
|
||||||
emptyNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if needsShimmerNode {
|
|
||||||
strongSelf.shimmerNodeOffset = shimmerNodeOffset
|
|
||||||
if strongSelf.emptyShimmerEffectNode == nil {
|
|
||||||
let emptyShimmerEffectNode = ChatListShimmerNode()
|
|
||||||
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
|
|
||||||
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
|
|
||||||
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
|
|
||||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
|
||||||
strongSelf.emptyShimmerEffectNode = nil
|
|
||||||
let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut)
|
|
||||||
emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in
|
|
||||||
emptyShimmerEffectNode?.removeFromSupernode()
|
|
||||||
})
|
|
||||||
strongSelf.listNode.alpha = 0.0
|
|
||||||
emptyNodeTransition.updateAlpha(node: strongSelf.listNode, alpha: 1.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
strongSelf.floatingHeaderOffset = offset
|
|
||||||
if let (size, insets, _, _, _, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
|
|
||||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
|
|
||||||
}
|
|
||||||
strongSelf.layoutAdditionalPanels(transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let filter, case let .filter(id, _, _, data) = filter, data.isShared {
|
|
||||||
self.pollFilterUpdatesDisposable = self.context.engine.peers.pollChatFolderUpdates(folderId: id).start()
|
|
||||||
self.chatFilterUpdatesDisposable = (self.context.engine.peers.subscribedChatFolderUpdates(folderId: id)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var update = false
|
|
||||||
if let result, result.availableChatsToJoin != 0 {
|
|
||||||
if self.chatFolderUpdates?.availableChatsToJoin != result.availableChatsToJoin {
|
|
||||||
update = true
|
|
||||||
}
|
|
||||||
self.chatFolderUpdates = result
|
|
||||||
} else {
|
|
||||||
if self.chatFolderUpdates != nil {
|
|
||||||
self.chatFolderUpdates = nil
|
|
||||||
update = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if update {
|
|
||||||
if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout {
|
|
||||||
self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .animated(duration: 0.4, curve: .spring))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .forum(peerId) = location {
|
|
||||||
self.peerDataDisposable = (context.engine.data.subscribe(
|
|
||||||
TelegramEngine.EngineData.Item.Peer.StatusSettings(id: peerId)
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] statusSettings in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var canReportPeer = false
|
|
||||||
if let statusSettings, statusSettings.flags.contains(.canReport) {
|
|
||||||
canReportPeer = true
|
|
||||||
}
|
|
||||||
if self.canReportPeer != canReportPeer {
|
|
||||||
self.canReportPeer = canReportPeer
|
|
||||||
if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset) = self.validLayout {
|
|
||||||
self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .animated(duration: 0.4, curve: .spring))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.pollFilterUpdatesDisposable?.dispose()
|
|
||||||
self.chatFilterUpdatesDisposable?.dispose()
|
|
||||||
self.peerDataDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, isInlineMode: self.isInlineMode, presentationData: self.presentationData, transition: .immediate)
|
|
||||||
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func layoutAdditionalPanels(transition: ContainedViewLayoutTransition) {
|
|
||||||
guard let (size, insets, visualNavigationHeight, _, _, _, _) = self.validLayout, let offset = self.floatingHeaderOffset else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = size
|
|
||||||
let _ = insets
|
|
||||||
|
|
||||||
if let topPanel = self.topPanel, let topPanelSize = topPanel.size {
|
|
||||||
let minY: CGFloat = visualNavigationHeight - 44.0 + topPanelSize.height
|
|
||||||
|
|
||||||
if let topPanelView = topPanel.view.view {
|
|
||||||
var animateIn = false
|
|
||||||
var topPanelTransition = transition
|
|
||||||
if topPanelView.bounds.isEmpty {
|
|
||||||
topPanelTransition = .immediate
|
|
||||||
animateIn = true
|
|
||||||
}
|
|
||||||
topPanelTransition.updateFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: max(minY, offset - topPanelSize.height)), size: topPanelSize))
|
|
||||||
if animateIn {
|
|
||||||
transition.animatePositionAdditive(layer: topPanelView.layer, offset: CGPoint(x: 0.0, y: -topPanelView.bounds.height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
|
||||||
self.presentationData = presentationData
|
|
||||||
|
|
||||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
|
||||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
|
||||||
}
|
|
||||||
|
|
||||||
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
|
||||||
|
|
||||||
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, storiesInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.validLayout = (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction, storiesInset)
|
|
||||||
|
|
||||||
var listInsets = insets
|
|
||||||
var additionalTopInset: CGFloat = 0.0
|
|
||||||
|
|
||||||
if let chatFolderUpdates = self.chatFolderUpdates {
|
|
||||||
let topPanel: TopPanelItem
|
|
||||||
var topPanelTransition = Transition(transition)
|
|
||||||
if let current = self.topPanel {
|
|
||||||
topPanel = current
|
|
||||||
} else {
|
|
||||||
topPanelTransition = .immediate
|
|
||||||
topPanel = TopPanelItem()
|
|
||||||
self.topPanel = topPanel
|
|
||||||
}
|
|
||||||
|
|
||||||
let title: String = self.presentationData.strings.ChatList_PanelNewChatsAvailable(Int32(chatFolderUpdates.availableChatsToJoin))
|
|
||||||
|
|
||||||
let topPanelHeight: CGFloat = 44.0
|
|
||||||
|
|
||||||
let _ = topPanel.view.update(
|
|
||||||
transition: topPanelTransition,
|
|
||||||
component: AnyComponent(ActionPanelComponent(
|
|
||||||
theme: self.presentationData.theme,
|
|
||||||
title: title,
|
|
||||||
color: .accent,
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.listNode.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(chatFolderUpdates), contents: chatFolderUpdates.chatFolderLinkContents))
|
|
||||||
},
|
|
||||||
dismissAction: { [weak self] in
|
|
||||||
guard let self, let chatFolderUpdates = self.chatFolderUpdates else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = self.context.engine.peers.hideChatFolderUpdates(folderId: chatFolderUpdates.folderId).start()
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: size.width, height: topPanelHeight)
|
|
||||||
)
|
|
||||||
if let topPanelView = topPanel.view.view {
|
|
||||||
if topPanelView.superview == nil {
|
|
||||||
self.view.addSubview(topPanelView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
topPanel.size = CGSize(width: size.width, height: topPanelHeight)
|
|
||||||
listInsets.top += topPanelHeight
|
|
||||||
additionalTopInset += topPanelHeight
|
|
||||||
} else if self.canReportPeer {
|
|
||||||
let topPanel: TopPanelItem
|
|
||||||
var topPanelTransition = Transition(transition)
|
|
||||||
if let current = self.topPanel {
|
|
||||||
topPanel = current
|
|
||||||
} else {
|
|
||||||
topPanelTransition = .immediate
|
|
||||||
topPanel = TopPanelItem()
|
|
||||||
self.topPanel = topPanel
|
|
||||||
}
|
|
||||||
|
|
||||||
let title: String = self.presentationData.strings.Conversation_ReportSpamAndLeave
|
|
||||||
|
|
||||||
let topPanelHeight: CGFloat = 44.0
|
|
||||||
|
|
||||||
let _ = topPanel.view.update(
|
|
||||||
transition: topPanelTransition,
|
|
||||||
component: AnyComponent(ActionPanelComponent(
|
|
||||||
theme: self.presentationData.theme,
|
|
||||||
title: title,
|
|
||||||
color: .destructive,
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self, case let .forum(peerId) = self.location else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
|
||||||
actionSheet.setItemGroups([
|
|
||||||
ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetTextItem(title: self.presentationData.strings.Conversation_ReportSpamGroupConfirmation),
|
|
||||||
ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ReportSpamAndLeave, color: .destructive, action: { [weak self, weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
self.controller?.setInlineChatList(location: nil)
|
|
||||||
let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: true).start()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
self.listNode.present?(actionSheet)
|
|
||||||
},
|
|
||||||
dismissAction: { [weak self] in
|
|
||||||
guard let self, case let .forum(peerId) = self.location else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = self.context.engine.peers.dismissPeerStatusOptions(peerId: peerId).start()
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: size.width, height: topPanelHeight)
|
|
||||||
)
|
|
||||||
if let topPanelView = topPanel.view.view {
|
|
||||||
if topPanelView.superview == nil {
|
|
||||||
self.view.addSubview(topPanelView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
topPanel.size = CGSize(width: size.width, height: topPanelHeight)
|
|
||||||
listInsets.top += topPanelHeight
|
|
||||||
additionalTopInset += topPanelHeight
|
|
||||||
} else {
|
|
||||||
if let topPanel = self.topPanel {
|
|
||||||
self.topPanel = nil
|
|
||||||
if let topPanelView = topPanel.view.view {
|
|
||||||
transition.updatePosition(layer: topPanelView.layer, position: CGPoint(x: topPanelView.layer.position.x, y: topPanelView.layer.position.y - topPanelView.layer.bounds.height), completion: { [weak topPanelView] _ in
|
|
||||||
topPanelView?.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: listInsets, duration: duration, curve: curve)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight + additionalTopInset, originalTopInset: originalNavigationHeight + additionalTopInset, storiesInset: storiesInset, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction)
|
|
||||||
|
|
||||||
if let emptyNode = self.emptyNode {
|
|
||||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
|
||||||
transition.updateFrame(node: emptyNode, frame: emptyNodeFrame)
|
|
||||||
emptyNode.updateLayout(size: emptyNodeFrame.size, insets: listInsets, transition: transition)
|
|
||||||
|
|
||||||
if let scrollingOffset = self.scrollingOffset {
|
|
||||||
emptyNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layoutAdditionalPanels(transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.scrollingOffset = (navigationHeight, offset)
|
|
||||||
|
|
||||||
if let emptyNode = self.emptyNode {
|
|
||||||
emptyNode.updateScrollingOffset(navigationHeight: navigationHeight, offset: offset, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private weak var controller: ChatListControllerImpl?
|
private weak var controller: ChatListControllerImpl?
|
||||||
|
@ -2345,19 +2345,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
for item in items {
|
for item in items {
|
||||||
switch item {
|
switch item {
|
||||||
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _, _):
|
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _, _):
|
||||||
if case .user = peer {
|
storyStatsIds.append(peer.id)
|
||||||
storyStatsIds.append(peer.id)
|
|
||||||
}
|
|
||||||
case let .localPeer(peer, _, _, _, _, _, _, _, _, _):
|
case let .localPeer(peer, _, _, _, _, _, _, _, _, _):
|
||||||
if case .user = peer {
|
storyStatsIds.append(peer.id)
|
||||||
storyStatsIds.append(peer.id)
|
|
||||||
}
|
|
||||||
case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _):
|
case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _):
|
||||||
if foundPeer.peer is TelegramUser {
|
storyStatsIds.append(foundPeer.peer.id)
|
||||||
storyStatsIds.append(foundPeer.peer.id)
|
|
||||||
}
|
|
||||||
case let .message(_, peer, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(_, peer, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let peer = peer.peer, case .user = peer {
|
if let peer = peer.peer {
|
||||||
storyStatsIds.append(peer.id)
|
storyStatsIds.append(peer.id)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -3386,7 +3380,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ShimmerEffectNode: ASDisplayNode {
|
private final class SearchShimmerEffectNode: ASDisplayNode {
|
||||||
private var currentBackgroundColor: UIColor?
|
private var currentBackgroundColor: UIColor?
|
||||||
private var currentForegroundColor: UIColor?
|
private var currentForegroundColor: UIColor?
|
||||||
private let imageNodeContainer: ASDisplayNode
|
private let imageNodeContainer: ASDisplayNode
|
||||||
@ -3504,13 +3498,13 @@ private final class ShimmerEffectNode: ASDisplayNode {
|
|||||||
|
|
||||||
public final class ChatListSearchShimmerNode: ASDisplayNode {
|
public final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||||
private let backgroundColorNode: ASDisplayNode
|
private let backgroundColorNode: ASDisplayNode
|
||||||
private let effectNode: ShimmerEffectNode
|
private let effectNode: SearchShimmerEffectNode
|
||||||
private let maskNode: ASImageNode
|
private let maskNode: ASImageNode
|
||||||
private var currentParams: (size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey)?
|
private var currentParams: (size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey)?
|
||||||
|
|
||||||
public init(key: ChatListSearchPaneKey) {
|
public init(key: ChatListSearchPaneKey) {
|
||||||
self.backgroundColorNode = ASDisplayNode()
|
self.backgroundColorNode = ASDisplayNode()
|
||||||
self.effectNode = ShimmerEffectNode()
|
self.effectNode = SearchShimmerEffectNode()
|
||||||
self.maskNode = ASImageNode()
|
self.maskNode = ASImageNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
288
submodules/ChatListUI/Sources/ChatListShimmerNode.swift
Normal file
288
submodules/ChatListUI/Sources/ChatListShimmerNode.swift
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
final class ShimmerEffectNode: ASDisplayNode {
|
||||||
|
private var currentBackgroundColor: UIColor?
|
||||||
|
private var currentForegroundColor: UIColor?
|
||||||
|
private let imageNodeContainer: ASDisplayNode
|
||||||
|
private let imageNode: ASImageNode
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
private var isCurrentlyInHierarchy = false
|
||||||
|
private var shouldBeAnimating = false
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.imageNodeContainer = ASDisplayNode()
|
||||||
|
self.imageNodeContainer.isLayerBacked = true
|
||||||
|
|
||||||
|
self.imageNode = ASImageNode()
|
||||||
|
self.imageNode.isLayerBacked = true
|
||||||
|
self.imageNode.displaysAsynchronously = false
|
||||||
|
self.imageNode.displayWithoutProcessing = true
|
||||||
|
self.imageNode.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isLayerBacked = true
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||||
|
self.addSubnode(self.imageNodeContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnterHierarchy() {
|
||||||
|
super.didEnterHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = true
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didExitHierarchy() {
|
||||||
|
super.didExitHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = false
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||||
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentBackgroundColor = backgroundColor
|
||||||
|
self.currentForegroundColor = foregroundColor
|
||||||
|
|
||||||
|
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
||||||
|
context.setFillColor(backgroundColor.cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||||
|
let peakColor = foregroundColor.cgColor
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||||
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||||
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
|
||||||
|
if sizeUpdated {
|
||||||
|
if self.shouldBeAnimating {
|
||||||
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
self.addImageAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameUpdated {
|
||||||
|
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAnimation() {
|
||||||
|
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||||
|
if shouldBeAnimating != self.shouldBeAnimating {
|
||||||
|
self.shouldBeAnimating = shouldBeAnimating
|
||||||
|
if shouldBeAnimating {
|
||||||
|
self.addImageAnimation()
|
||||||
|
} else {
|
||||||
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addImageAnimation() {
|
||||||
|
guard let containerSize = self.absoluteLocation?.1 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let gradientHeight: CGFloat = 250.0
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||||
|
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||||
|
animation.repeatCount = Float.infinity
|
||||||
|
animation.beginTime = 1.0
|
||||||
|
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ChatListShimmerNode: ASDisplayNode {
|
||||||
|
private let backgroundColorNode: ASDisplayNode
|
||||||
|
private let effectNode: ShimmerEffectNode
|
||||||
|
private let maskNode: ASImageNode
|
||||||
|
private var currentParams: (size: CGSize, presentationData: PresentationData)?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.backgroundColorNode = ASDisplayNode()
|
||||||
|
self.effectNode = ShimmerEffectNode()
|
||||||
|
self.maskNode = ASImageNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundColorNode)
|
||||||
|
self.addSubnode(self.effectNode)
|
||||||
|
self.addSubnode(self.maskNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, isInlineMode: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||||
|
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
|
||||||
|
self.currentParams = (size, presentationData)
|
||||||
|
|
||||||
|
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
|
let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil))
|
||||||
|
let timestamp1: Int32 = 100000
|
||||||
|
let peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||||
|
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||||
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
|
gesture?.cancel()
|
||||||
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in })
|
||||||
|
interaction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
|
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||||
|
let message = EngineMessage(
|
||||||
|
stableId: 0,
|
||||||
|
stableVersion: 0,
|
||||||
|
id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0),
|
||||||
|
globallyUniqueId: nil,
|
||||||
|
groupingKey: nil,
|
||||||
|
groupInfo: nil,
|
||||||
|
threadId: nil,
|
||||||
|
timestamp: timestamp1,
|
||||||
|
flags: [],
|
||||||
|
tags: [],
|
||||||
|
globalTags: [],
|
||||||
|
localTags: [],
|
||||||
|
forwardInfo: nil,
|
||||||
|
author: peer1,
|
||||||
|
text: "Text",
|
||||||
|
attributes: [],
|
||||||
|
media: [],
|
||||||
|
peers: peers,
|
||||||
|
associatedMessages: [:],
|
||||||
|
associatedMessageIds: [],
|
||||||
|
associatedMedia: [:],
|
||||||
|
associatedThreadInfo: nil,
|
||||||
|
associatedStories: [:]
|
||||||
|
)
|
||||||
|
let readState = EnginePeerReadCounters()
|
||||||
|
|
||||||
|
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(ChatListItemContent.PeerData(
|
||||||
|
messages: [message],
|
||||||
|
peer: EngineRenderedPeer(peer: peer1),
|
||||||
|
threadInfo: nil,
|
||||||
|
combinedReadState: readState,
|
||||||
|
isRemovedFromTotalUnreadCount: false,
|
||||||
|
presence: nil,
|
||||||
|
hasUnseenMentions: false,
|
||||||
|
hasUnseenReactions: false,
|
||||||
|
draftState: nil,
|
||||||
|
inputActivities: nil,
|
||||||
|
promoInfo: nil,
|
||||||
|
ignoreUnreadBadge: false,
|
||||||
|
displayAsMessage: false,
|
||||||
|
hasFailedMessages: false,
|
||||||
|
forumTopicData: nil,
|
||||||
|
topForumTopicItems: [],
|
||||||
|
autoremoveTimeout: nil,
|
||||||
|
storyState: nil
|
||||||
|
)), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemNodes: [ChatListItemNode] = []
|
||||||
|
for i in 0 ..< items.count {
|
||||||
|
items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
||||||
|
if let itemNode = node as? ChatListItemNode {
|
||||||
|
itemNodes.append(itemNode)
|
||||||
|
}
|
||||||
|
apply().1(ListViewItemApply(isOnScreen: true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor
|
||||||
|
|
||||||
|
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
var currentY: CGFloat = 0.0
|
||||||
|
let fakeLabelPlaceholderHeight: CGFloat = 8.0
|
||||||
|
|
||||||
|
func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) {
|
||||||
|
let startPoint = origin
|
||||||
|
let diameter = fakeLabelPlaceholderHeight
|
||||||
|
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
|
||||||
|
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
|
||||||
|
}
|
||||||
|
|
||||||
|
while currentY < size.height {
|
||||||
|
let sampleIndex = 0
|
||||||
|
let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height
|
||||||
|
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
|
||||||
|
if !isInlineMode {
|
||||||
|
if !itemNodes[sampleIndex].avatarNode.isHidden {
|
||||||
|
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.view.convert(itemNodes[sampleIndex].avatarNode.bounds, to: itemNodes[sampleIndex].view).offsetBy(dx: 0.0, dy: currentY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
||||||
|
if isInlineMode {
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 22.0, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0 - 22.0)
|
||||||
|
} else {
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
||||||
|
|
||||||
|
if isInlineMode {
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: textFrame.minX, y: titleFrame.minY + 2.0), size: CGSize(width: 16.0, height: 16.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0)
|
||||||
|
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0)
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0)
|
||||||
|
|
||||||
|
let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
||||||
|
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
|
||||||
|
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
|
||||||
|
context.fill(itemNodes[sampleIndex].separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
|
||||||
|
|
||||||
|
currentY += itemHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
||||||
|
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||||
|
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||||
|
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||||
|
}
|
||||||
|
}
|
@ -23,8 +23,6 @@ import ChatListHeaderComponent
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import NewSessionInfoScreen
|
import NewSessionInfoScreen
|
||||||
|
|
||||||
private var debugDidAddNewSessionReview = false
|
|
||||||
|
|
||||||
public enum ChatListNodeMode {
|
public enum ChatListNodeMode {
|
||||||
case chatList(appendContacts: Bool)
|
case chatList(appendContacts: Bool)
|
||||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, displayPresence: Bool)
|
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, displayPresence: Bool)
|
||||||
@ -1637,11 +1635,11 @@ public final class ChatListNode: ListView {
|
|||||||
return true
|
return true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
let _ = removeNewSessionReviews(postbox: self.context.account.postbox, ids: [newSessionReview.id]).start()
|
let _ = self.context.engine.privacy.confirmNewSessionReview(id: newSessionReview.id)
|
||||||
} else {
|
} else {
|
||||||
self.push?(NewSessionInfoScreen(context: self.context, newSessionReview: newSessionReview))
|
self.push?(NewSessionInfoScreen(context: self.context, newSessionReview: newSessionReview))
|
||||||
|
|
||||||
let _ = removeNewSessionReviews(postbox: self.context.account.postbox, ids: [newSessionReview.id]).start()
|
let _ = self.context.engine.privacy.terminateAnotherSession(id: newSessionReview.id).start()
|
||||||
}
|
}
|
||||||
}, openChatFolderUpdates: { [weak self] in
|
}, openChatFolderUpdates: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1743,12 +1741,7 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
let suggestedChatListNotice: Signal<ChatListNotice?, NoError>
|
let suggestedChatListNotice: Signal<ChatListNotice?, NoError>
|
||||||
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||||
#if DEBUG
|
let _ = context.engine.privacy.cleanupSessionReviews().start()
|
||||||
if !debugDidAddNewSessionReview {
|
|
||||||
debugDidAddNewSessionReview = true
|
|
||||||
let _ = addNewSessionReview(postbox: context.account.postbox, item: NewSessionReview(id: 1, device: "iPhone 14 Pro", location: "Dubai, UAE")).start()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
suggestedChatListNotice = .single(nil)
|
suggestedChatListNotice = .single(nil)
|
||||||
|> then (
|
|> then (
|
||||||
@ -2952,6 +2945,15 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.dynamicVisualInsets = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return UIEdgeInsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
return UIEdgeInsets()
|
||||||
|
}
|
||||||
|
|
||||||
self.pollFilterUpdates()
|
self.pollFilterUpdates()
|
||||||
self.resetFilter()
|
self.resetFilter()
|
||||||
|
|
||||||
|
@ -112,12 +112,12 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.contentContainer.clipsToBounds = true
|
self.contentContainer.clipsToBounds = true
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
|
|
||||||
self.addSubnode(self.separatorNode)
|
|
||||||
self.contentContainer.addSubnode(self.titleNode)
|
self.contentContainer.addSubnode(self.titleNode)
|
||||||
self.contentContainer.addSubnode(self.textNode)
|
self.contentContainer.addSubnode(self.textNode)
|
||||||
self.contentContainer.addSubnode(self.arrowNode)
|
self.contentContainer.addSubnode(self.arrowNode)
|
||||||
|
|
||||||
self.addSubnode(self.contentContainer)
|
self.addSubnode(self.contentContainer)
|
||||||
|
self.addSubnode(self.separatorNode)
|
||||||
|
|
||||||
self.zPosition = 1.0
|
self.zPosition = 1.0
|
||||||
}
|
}
|
||||||
|
@ -189,6 +189,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
public private(set) final var visibleSize: CGSize = CGSize()
|
public private(set) final var visibleSize: CGSize = CGSize()
|
||||||
public private(set) final var insets = UIEdgeInsets()
|
public private(set) final var insets = UIEdgeInsets()
|
||||||
public final var visualInsets: UIEdgeInsets?
|
public final var visualInsets: UIEdgeInsets?
|
||||||
|
public final var dynamicVisualInsets: (() -> UIEdgeInsets)?
|
||||||
public private(set) final var headerInsets = UIEdgeInsets()
|
public private(set) final var headerInsets = UIEdgeInsets()
|
||||||
public private(set) final var scrollIndicatorInsets = UIEdgeInsets()
|
public private(set) final var scrollIndicatorInsets = UIEdgeInsets()
|
||||||
private final var ensureTopInsetForOverlayHighlightedItems: CGFloat?
|
private final var ensureTopInsetForOverlayHighlightedItems: CGFloat?
|
||||||
@ -4388,7 +4389,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||||||
if abs(apparentHeightDelta) > CGFloat.ulpOfOne {
|
if abs(apparentHeightDelta) > CGFloat.ulpOfOne {
|
||||||
itemNode.updateFrame(itemNode.frame, within: self.visibleSize)
|
itemNode.updateFrame(itemNode.frame, within: self.visibleSize)
|
||||||
|
|
||||||
let visualInsets = self.visualInsets ?? self.insets
|
let visualInsets = self.dynamicVisualInsets?() ?? self.visualInsets ?? self.insets
|
||||||
|
|
||||||
if itemNode.apparentFrame.maxY <= visualInsets.top {
|
if itemNode.apparentFrame.maxY <= visualInsets.top {
|
||||||
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
|
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
|
||||||
|
@ -20,7 +20,7 @@ private let rankMaxLength: Int32 = 16
|
|||||||
private final class ChannelAdminControllerArguments {
|
private final class ChannelAdminControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let updateAdminRights: (Bool) -> Void
|
let updateAdminRights: (Bool) -> Void
|
||||||
let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
let toggleRight: (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void
|
||||||
let toggleRightWhileDisabled: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
let toggleRightWhileDisabled: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
||||||
let transferOwnership: () -> Void
|
let transferOwnership: () -> Void
|
||||||
let updateRank: (String, String) -> Void
|
let updateRank: (String, String) -> Void
|
||||||
@ -28,8 +28,9 @@ private final class ChannelAdminControllerArguments {
|
|||||||
let dismissAdmin: () -> Void
|
let dismissAdmin: () -> Void
|
||||||
let dismissInput: () -> Void
|
let dismissInput: () -> Void
|
||||||
let animateError: () -> Void
|
let animateError: () -> Void
|
||||||
|
let toggleIsOptionExpanded: (RightsItem.Sub) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void) {
|
init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updateAdminRights = updateAdminRights
|
self.updateAdminRights = updateAdminRights
|
||||||
self.toggleRight = toggleRight
|
self.toggleRight = toggleRight
|
||||||
@ -40,6 +41,7 @@ private final class ChannelAdminControllerArguments {
|
|||||||
self.dismissAdmin = dismissAdmin
|
self.dismissAdmin = dismissAdmin
|
||||||
self.dismissInput = dismissInput
|
self.dismissInput = dismissInput
|
||||||
self.animateError = animateError
|
self.animateError = animateError
|
||||||
|
self.toggleIsOptionExpanded = toggleIsOptionExpanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +73,41 @@ private enum ChannelAdminEntryStableId: Hashable {
|
|||||||
case rankInfo
|
case rankInfo
|
||||||
case adminRights
|
case adminRights
|
||||||
case rightsTitle
|
case rightsTitle
|
||||||
case right(TelegramChatAdminRightsFlags)
|
case right(RightsItem)
|
||||||
case addAdminsInfo
|
case addAdminsInfo
|
||||||
case transfer
|
case transfer
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct AdminSubPermission: Equatable {
|
||||||
|
var title: String
|
||||||
|
var flags: TelegramChatAdminRightsFlags
|
||||||
|
var isSelected: Bool
|
||||||
|
var isEnabled: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RightsItem: Equatable, Hashable {
|
||||||
|
enum Sub {
|
||||||
|
case messages
|
||||||
|
case stories
|
||||||
|
}
|
||||||
|
|
||||||
|
case direct(TelegramChatAdminRightsFlags)
|
||||||
|
case sub(Sub, [TelegramChatAdminRightsFlags])
|
||||||
|
}
|
||||||
|
|
||||||
|
private let messageRelatedFlags: [TelegramChatAdminRightsFlags] = [
|
||||||
|
.canPostMessages,
|
||||||
|
.canEditMessages,
|
||||||
|
.canDeleteMessages
|
||||||
|
]
|
||||||
|
|
||||||
|
private let storiesRelatedFlags: [TelegramChatAdminRightsFlags] = [
|
||||||
|
.canPostStories,
|
||||||
|
.canEditStories,
|
||||||
|
.canDeleteStories
|
||||||
|
]
|
||||||
|
|
||||||
private enum ChannelAdminEntry: ItemListNodeEntry {
|
private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||||
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, EnginePeer.Presence?)
|
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, EnginePeer.Presence?)
|
||||||
case rankTitle(PresentationTheme, String, Int32?, Int32)
|
case rankTitle(PresentationTheme, String, Int32?, Int32)
|
||||||
@ -84,7 +115,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
case rankInfo(PresentationTheme, String, Bool)
|
case rankInfo(PresentationTheme, String, Bool)
|
||||||
case adminRights(PresentationTheme, String, Bool)
|
case adminRights(PresentationTheme, String, Bool)
|
||||||
case rightsTitle(PresentationTheme, String)
|
case rightsTitle(PresentationTheme, String)
|
||||||
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
|
case rightItem(PresentationTheme, Int, String, RightsItem, TelegramChatAdminRightsFlags, Bool, Bool, [AdminSubPermission], Bool)
|
||||||
case addAdminsInfo(PresentationTheme, String)
|
case addAdminsInfo(PresentationTheme, String)
|
||||||
case transfer(PresentationTheme, String)
|
case transfer(PresentationTheme, String)
|
||||||
case dismiss(PresentationTheme, String)
|
case dismiss(PresentationTheme, String)
|
||||||
@ -120,7 +151,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
return .adminRights
|
return .adminRights
|
||||||
case .rightsTitle:
|
case .rightsTitle:
|
||||||
return .rightsTitle
|
return .rightsTitle
|
||||||
case let .rightItem(_, _, _, right, _, _, _):
|
case let .rightItem(_, _, _, right, _, _, _, _, _):
|
||||||
return .right(right)
|
return .right(right)
|
||||||
case .addAdminsInfo:
|
case .addAdminsInfo:
|
||||||
return .addAdminsInfo
|
return .addAdminsInfo
|
||||||
@ -185,8 +216,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .rightItem(lhsTheme, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled):
|
case let .rightItem(lhsTheme, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled, lhsSubItems, lhsIsExpanded):
|
||||||
if case let .rightItem(rhsTheme, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled) = rhs {
|
if case let .rightItem(rhsTheme, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled, rhsSubItems, rhsIsExpanded) = rhs {
|
||||||
if lhsTheme !== rhsTheme {
|
if lhsTheme !== rhsTheme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -208,6 +239,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
if lhsEnabled != rhsEnabled {
|
if lhsEnabled != rhsEnabled {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsSubItems != rhsSubItems {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsIsExpanded != rhsIsExpanded {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -256,11 +293,11 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .rightItem(_, lhsIndex, _, _, _, _, _):
|
case let .rightItem(_, lhsIndex, _, _, _, _, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .info, .adminRights, .rightsTitle:
|
case .info, .adminRights, .rightsTitle:
|
||||||
return false
|
return false
|
||||||
case let .rightItem(_, rhsIndex, _, _, _, _, _):
|
case let .rightItem(_, rhsIndex, _, _, _, _, _, _, _):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
@ -341,12 +378,48 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
case let .rightsTitle(_, text):
|
case let .rightsTitle(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .rightItem(_, _, text, right, flags, value, enabled):
|
case let .rightItem(_, _, text, right, flags, value, enabled, subPermissions, isExpanded):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { _ in
|
if !subPermissions.isEmpty {
|
||||||
arguments.toggleRight(right, flags)
|
return ItemListExpandableSwitchItem(presentationData: presentationData, title: text, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in
|
||||||
}, activatedWhileDisabled: {
|
return ItemListExpandableSwitchItem.SubItem(
|
||||||
arguments.toggleRightWhileDisabled(right, flags)
|
id: AnyHashable(item.flags.rawValue),
|
||||||
})
|
title: item.title,
|
||||||
|
isSelected: item.isSelected,
|
||||||
|
isEnabled: item.isEnabled
|
||||||
|
)
|
||||||
|
}, type: .icon, enableInteractiveChanges: enabled, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
if enabled {
|
||||||
|
arguments.toggleRight(right, flags, value)
|
||||||
|
} else {
|
||||||
|
//arguments.toggleRightWhileDisabled(right, flags)
|
||||||
|
}
|
||||||
|
}, activatedWhileDisabled: {
|
||||||
|
//arguments.toggleRightWhileDisabled(right, flags)
|
||||||
|
}, selectAction: {
|
||||||
|
if case let .sub(type, _) = right {
|
||||||
|
arguments.toggleIsOptionExpanded(type)
|
||||||
|
}
|
||||||
|
}, subAction: { item in
|
||||||
|
guard let value = item.id.base as? Int32 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let subRights = TelegramChatAdminRightsFlags(rawValue: value)
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
arguments.toggleRight(.direct(subRights), flags, !item.isSelected)
|
||||||
|
} else {
|
||||||
|
arguments.toggleRightWhileDisabled(subRights, flags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
arguments.toggleRight(right, flags, value)
|
||||||
|
}, activatedWhileDisabled: {
|
||||||
|
if case let .direct(right) = right {
|
||||||
|
arguments.toggleRightWhileDisabled(right, flags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
case let .addAdminsInfo(_, text):
|
case let .addAdminsInfo(_, text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .transfer(_, text):
|
case let .transfer(_, text):
|
||||||
@ -362,18 +435,20 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct ChannelAdminControllerState: Equatable {
|
private struct ChannelAdminControllerState: Equatable {
|
||||||
let adminRights: Bool
|
var adminRights: Bool
|
||||||
let updatedFlags: TelegramChatAdminRightsFlags?
|
var updatedFlags: TelegramChatAdminRightsFlags?
|
||||||
let updatedRank: String?
|
var updatedRank: String?
|
||||||
let updating: Bool
|
var updating: Bool
|
||||||
let focusedOnRank: Bool
|
var focusedOnRank: Bool
|
||||||
|
var expandedPermissions: Set<RightsItem.Sub> = Set()
|
||||||
|
|
||||||
init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, updating: Bool = false, focusedOnRank: Bool = false) {
|
init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, updating: Bool = false, focusedOnRank: Bool = false, expandedPermissions: Set<RightsItem.Sub> = Set()) {
|
||||||
self.adminRights = adminRights
|
self.adminRights = adminRights
|
||||||
self.updatedFlags = updatedFlags
|
self.updatedFlags = updatedFlags
|
||||||
self.updatedRank = updatedRank
|
self.updatedRank = updatedRank
|
||||||
self.updating = updating
|
self.updating = updating
|
||||||
self.focusedOnRank = focusedOnRank
|
self.focusedOnRank = focusedOnRank
|
||||||
|
self.expandedPermissions = expandedPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ChannelAdminControllerState, rhs: ChannelAdminControllerState) -> Bool {
|
static func ==(lhs: ChannelAdminControllerState, rhs: ChannelAdminControllerState) -> Bool {
|
||||||
@ -392,27 +467,30 @@ private struct ChannelAdminControllerState: Equatable {
|
|||||||
if lhs.focusedOnRank != rhs.focusedOnRank {
|
if lhs.focusedOnRank != rhs.focusedOnRank {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.expandedPermissions != rhs.expandedPermissions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedAdminRights(_ adminRights: Bool) -> ChannelAdminControllerState {
|
func withUpdatedAdminRights(_ adminRights: Bool) -> ChannelAdminControllerState {
|
||||||
return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank)
|
return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChatAdminRightsFlags?) -> ChannelAdminControllerState {
|
func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChatAdminRightsFlags?) -> ChannelAdminControllerState {
|
||||||
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank)
|
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedUpdatedRank(_ updatedRank: String?) -> ChannelAdminControllerState {
|
func withUpdatedUpdatedRank(_ updatedRank: String?) -> ChannelAdminControllerState {
|
||||||
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank)
|
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedUpdating(_ updating: Bool) -> ChannelAdminControllerState {
|
func withUpdatedUpdating(_ updating: Bool) -> ChannelAdminControllerState {
|
||||||
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: updating, focusedOnRank: self.focusedOnRank)
|
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedFocusedOnRank(_ focusedOnRank: Bool) -> ChannelAdminControllerState {
|
func withUpdatedFocusedOnRank(_ focusedOnRank: Bool) -> ChannelAdminControllerState {
|
||||||
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: focusedOnRank)
|
return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: focusedOnRank, expandedPermissions: self.expandedPermissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +500,12 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
|
|||||||
} else if right.contains(.canPostMessages) {
|
} else if right.contains(.canPostMessages) {
|
||||||
return strings.Channel_EditAdmin_PermissionPostMessages
|
return strings.Channel_EditAdmin_PermissionPostMessages
|
||||||
} else if right.contains(.canEditMessages) {
|
} else if right.contains(.canEditMessages) {
|
||||||
return strings.Channel_EditAdmin_PermissionEditMessages
|
if isChannel {
|
||||||
|
//TODO:localize
|
||||||
|
return "Edit Messages of Others"
|
||||||
|
} else {
|
||||||
|
return strings.Channel_EditAdmin_PermissionEditMessages
|
||||||
|
}
|
||||||
} else if right.contains(.canDeleteMessages) {
|
} else if right.contains(.canDeleteMessages) {
|
||||||
return isGroup ? strings.Channel_EditAdmin_PermissionDeleteMessages : strings.Channel_EditAdmin_PermissionDeleteMessagesOfOthers
|
return isGroup ? strings.Channel_EditAdmin_PermissionDeleteMessages : strings.Channel_EditAdmin_PermissionDeleteMessagesOfOthers
|
||||||
} else if right.contains(.canBanUsers) {
|
} else if right.contains(.canBanUsers) {
|
||||||
@ -451,6 +534,15 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
|
|||||||
} else {
|
} else {
|
||||||
return strings.Channel_AdminLog_CanManageCalls
|
return strings.Channel_AdminLog_CanManageCalls
|
||||||
}
|
}
|
||||||
|
} else if right.contains(.canPostStories) {
|
||||||
|
//TODO:localize
|
||||||
|
return "Post Stories"
|
||||||
|
} else if right.contains(.canEditStories) {
|
||||||
|
//TODO:localize
|
||||||
|
return "Edit Stories of Others"
|
||||||
|
} else if right.contains(.canDeleteStories) {
|
||||||
|
//TODO:localize
|
||||||
|
return "Delete Stories of Others"
|
||||||
} else {
|
} else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -553,45 +645,45 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
|
|
||||||
let isGroup: Bool
|
let isGroup: Bool
|
||||||
var maskRightsFlags: TelegramChatAdminRightsFlags
|
var maskRightsFlags: TelegramChatAdminRightsFlags
|
||||||
let rightsOrder: [TelegramChatAdminRightsFlags]
|
|
||||||
|
let rightsOrder: [RightsItem]
|
||||||
|
|
||||||
maskRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: .channel(channel))
|
maskRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: .channel(channel))
|
||||||
switch channel.info {
|
switch channel.info {
|
||||||
case .broadcast:
|
case .broadcast:
|
||||||
isGroup = false
|
isGroup = false
|
||||||
rightsOrder = [
|
rightsOrder = [
|
||||||
.canChangeInfo,
|
.direct(.canChangeInfo),
|
||||||
.canPostMessages,
|
.sub(.messages, messageRelatedFlags),
|
||||||
.canEditMessages,
|
.sub(.stories, storiesRelatedFlags),
|
||||||
.canDeleteMessages,
|
.direct(.canInviteUsers),
|
||||||
.canInviteUsers,
|
.direct(.canManageCalls),
|
||||||
.canManageCalls,
|
.direct(.canAddAdmins)
|
||||||
.canAddAdmins
|
|
||||||
]
|
]
|
||||||
case .group:
|
case .group:
|
||||||
isGroup = true
|
isGroup = true
|
||||||
if channel.flags.contains(.isForum) {
|
if channel.flags.contains(.isForum) {
|
||||||
rightsOrder = [
|
rightsOrder = [
|
||||||
.canChangeInfo,
|
.direct(.canChangeInfo),
|
||||||
.canDeleteMessages,
|
.direct(.canDeleteMessages),
|
||||||
.canBanUsers,
|
.direct(.canBanUsers),
|
||||||
.canInviteUsers,
|
.direct(.canInviteUsers),
|
||||||
.canPinMessages,
|
.direct(.canPinMessages),
|
||||||
.canManageTopics,
|
.direct(.canManageTopics),
|
||||||
.canManageCalls,
|
.direct(.canManageCalls),
|
||||||
.canBeAnonymous,
|
.direct(.canBeAnonymous),
|
||||||
.canAddAdmins
|
.direct(.canAddAdmins)
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
rightsOrder = [
|
rightsOrder = [
|
||||||
.canChangeInfo,
|
.direct(.canChangeInfo),
|
||||||
.canDeleteMessages,
|
.direct(.canDeleteMessages),
|
||||||
.canBanUsers,
|
.direct(.canBanUsers),
|
||||||
.canInviteUsers,
|
.direct(.canInviteUsers),
|
||||||
.canPinMessages,
|
.direct(.canPinMessages),
|
||||||
.canManageCalls,
|
.direct(.canManageCalls),
|
||||||
.canBeAnonymous,
|
.direct(.canBeAnonymous),
|
||||||
.canAddAdmins
|
.direct(.canAddAdmins)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -621,11 +713,52 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
for right in rightsOrder {
|
rightsLoop: for right in rightsOrder {
|
||||||
if accountUserRightsFlags.contains(right) {
|
let enabled: Bool
|
||||||
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous))
|
let isSelected: Bool
|
||||||
index += 1
|
let itemTitle: String
|
||||||
|
var subItems: [AdminSubPermission] = []
|
||||||
|
var isExpanded = false
|
||||||
|
|
||||||
|
switch right {
|
||||||
|
case let .direct(right):
|
||||||
|
if !accountUserRightsFlags.contains(right) {
|
||||||
|
continue rightsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled = right == .canBeAnonymous
|
||||||
|
|
||||||
|
itemTitle = stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights)
|
||||||
|
isSelected = currentRightsFlags.contains(right)
|
||||||
|
case let .sub(type, subRights):
|
||||||
|
let filteredSubRights = subRights.filter({ accountUserRightsFlags.contains($0) })
|
||||||
|
if filteredSubRights.isEmpty {
|
||||||
|
continue rightsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
switch type {
|
||||||
|
case .messages:
|
||||||
|
itemTitle = "Manage Messages"
|
||||||
|
case .stories:
|
||||||
|
itemTitle = "Manage Stories"
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected = subRights.allSatisfy({ currentRightsFlags.contains($0) })
|
||||||
|
|
||||||
|
isExpanded = state.expandedPermissions.contains(type)
|
||||||
|
|
||||||
|
for subRight in filteredSubRights {
|
||||||
|
let subRightEnabled = true
|
||||||
|
|
||||||
|
subItems.append(AdminSubPermission(title: stringForRight(strings: presentationData.strings, right: subRight, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), flags: subRight, isSelected: currentRightsFlags.contains(subRight), isEnabled: enabled && subRightEnabled))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries.append(.rightItem(presentationData.theme, index, itemTitle, right, currentRightsFlags, isSelected, enabled, subItems, isExpanded))
|
||||||
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -660,11 +793,52 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
for right in rightsOrder {
|
rightsLoop: for right in rightsOrder {
|
||||||
if accountUserRightsFlags.contains(right) {
|
let enabled: Bool
|
||||||
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: .channel(channel), right: right)))
|
let isSelected: Bool
|
||||||
index += 1
|
let itemTitle: String
|
||||||
|
var subItems: [AdminSubPermission] = []
|
||||||
|
var isExpanded = false
|
||||||
|
|
||||||
|
switch right {
|
||||||
|
case let .direct(right):
|
||||||
|
if !accountUserRightsFlags.contains(right) {
|
||||||
|
continue rightsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled = !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: .channel(channel), right: right)
|
||||||
|
|
||||||
|
itemTitle = stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights)
|
||||||
|
isSelected = currentRightsFlags.contains(right)
|
||||||
|
case let .sub(type, subRights):
|
||||||
|
let filteredSubRights = subRights.filter({ accountUserRightsFlags.contains($0) })
|
||||||
|
if filteredSubRights.isEmpty {
|
||||||
|
continue rightsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled = !state.updating
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
switch type {
|
||||||
|
case .messages:
|
||||||
|
itemTitle = "Manage Messages"
|
||||||
|
case .stories:
|
||||||
|
itemTitle = "Manage Stories"
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected = subRights.allSatisfy({ currentRightsFlags.contains($0) })
|
||||||
|
|
||||||
|
isExpanded = state.expandedPermissions.contains(type)
|
||||||
|
|
||||||
|
for subRight in filteredSubRights {
|
||||||
|
let subRightEnabled = !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: .channel(channel), right: subRight)
|
||||||
|
|
||||||
|
subItems.append(AdminSubPermission(title: stringForRight(strings: presentationData.strings, right: subRight, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), flags: subRight, isSelected: currentRightsFlags.contains(subRight), isEnabled: enabled && subRightEnabled))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries.append(.rightItem(presentationData.theme, index, itemTitle, right, currentRightsFlags, isSelected, enabled, subItems, isExpanded))
|
||||||
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountUserRightsFlags.contains(.canAddAdmins) {
|
if accountUserRightsFlags.contains(.canAddAdmins) {
|
||||||
@ -693,8 +867,43 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
}
|
}
|
||||||
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo {
|
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo {
|
||||||
var index = 0
|
var index = 0
|
||||||
for right in rightsOrder {
|
rightsLoop: for right in rightsOrder {
|
||||||
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, adminInfo.rights.rights, adminInfo.rights.rights.contains(right), false))
|
let enabled: Bool = false
|
||||||
|
let isSelected: Bool
|
||||||
|
let itemTitle: String
|
||||||
|
var subItems: [AdminSubPermission] = []
|
||||||
|
var isExpanded = false
|
||||||
|
|
||||||
|
switch right {
|
||||||
|
case let .direct(right):
|
||||||
|
itemTitle = stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights)
|
||||||
|
isSelected = adminInfo.rights.rights.contains(right)
|
||||||
|
case let .sub(type, subRights):
|
||||||
|
let filteredSubRights = subRights
|
||||||
|
if filteredSubRights.isEmpty {
|
||||||
|
continue rightsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
switch type {
|
||||||
|
case .messages:
|
||||||
|
itemTitle = "Manage Messages"
|
||||||
|
case .stories:
|
||||||
|
itemTitle = "Manage Stories"
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected = subRights.allSatisfy({ adminInfo.rights.rights.contains($0) })
|
||||||
|
|
||||||
|
isExpanded = state.expandedPermissions.contains(type)
|
||||||
|
|
||||||
|
for subRight in filteredSubRights {
|
||||||
|
let subRightEnabled = false
|
||||||
|
|
||||||
|
subItems.append(AdminSubPermission(title: stringForRight(strings: presentationData.strings, right: subRight, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), flags: subRight, isSelected: adminInfo.rights.rights.contains(subRight), isEnabled: enabled && subRightEnabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.rightItem(presentationData.theme, index, itemTitle, right, adminInfo.rights.rights, isSelected, enabled, subItems, isExpanded))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -792,7 +1001,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
var index = 0
|
var index = 0
|
||||||
for right in rightsOrder {
|
for right in rightsOrder {
|
||||||
if accountUserRightsFlags.contains(right) {
|
if accountUserRightsFlags.contains(right) {
|
||||||
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: false, defaultBannedRights: group.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && accountIsCreator))
|
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: false, defaultBannedRights: group.defaultBannedRights), .direct(right), currentRightsFlags, currentRightsFlags.contains(right), !state.updating && accountIsCreator, [], false))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -866,13 +1075,25 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||||||
updateState { current in
|
updateState { current in
|
||||||
return current.withUpdatedAdminRights(value)
|
return current.withUpdatedAdminRights(value)
|
||||||
}
|
}
|
||||||
}, toggleRight: { right, flags in
|
}, toggleRight: { right, flags, value in
|
||||||
updateState { current in
|
updateState { current in
|
||||||
var updated = flags
|
var updated = flags
|
||||||
if flags.contains(right) {
|
|
||||||
updated.remove(right)
|
var combinedRight: TelegramChatAdminRightsFlags
|
||||||
|
switch right {
|
||||||
|
case let .direct(right):
|
||||||
|
combinedRight = right
|
||||||
|
case let .sub(_, right):
|
||||||
|
combinedRight = []
|
||||||
|
for flag in right {
|
||||||
|
combinedRight.insert(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
updated.remove(combinedRight)
|
||||||
} else {
|
} else {
|
||||||
updated.insert(right)
|
updated.insert(combinedRight)
|
||||||
}
|
}
|
||||||
return current.withUpdatedUpdatedFlags(updated)
|
return current.withUpdatedUpdatedFlags(updated)
|
||||||
}
|
}
|
||||||
@ -971,6 +1192,18 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||||||
dismissInputImpl?()
|
dismissInputImpl?()
|
||||||
}, animateError: {
|
}, animateError: {
|
||||||
errorImpl?()
|
errorImpl?()
|
||||||
|
}, toggleIsOptionExpanded: { flag in
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
|
||||||
|
if state.expandedPermissions.contains(flag) {
|
||||||
|
state.expandedPermissions.remove(flag)
|
||||||
|
} else {
|
||||||
|
state.expandedPermissions.insert(flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||||
|
@ -160,7 +160,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-531931925] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsMentions($0) }
|
dict[-531931925] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsMentions($0) }
|
||||||
dict[-566281095] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsRecent($0) }
|
dict[-566281095] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsRecent($0) }
|
||||||
dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) }
|
dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) }
|
||||||
dict[-2094689180] = { return Api.Chat.parse_channel($0) }
|
dict[-1795845413] = { return Api.Chat.parse_channel($0) }
|
||||||
dict[399807445] = { return Api.Chat.parse_channelForbidden($0) }
|
dict[399807445] = { return Api.Chat.parse_channelForbidden($0) }
|
||||||
dict[1103884886] = { return Api.Chat.parse_chat($0) }
|
dict[1103884886] = { return Api.Chat.parse_chat($0) }
|
||||||
dict[693512293] = { return Api.Chat.parse_chatEmpty($0) }
|
dict[693512293] = { return Api.Chat.parse_chatEmpty($0) }
|
||||||
@ -911,7 +911,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) }
|
dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) }
|
||||||
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
|
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
|
||||||
dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) }
|
dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) }
|
||||||
dict[1757493555] = { return Api.Update.parse_updateReadMessagesContents($0) }
|
dict[-131960447] = { return Api.Update.parse_updateReadMessagesContents($0) }
|
||||||
dict[-145845461] = { return Api.Update.parse_updateReadStories($0) }
|
dict[-145845461] = { return Api.Update.parse_updateReadStories($0) }
|
||||||
dict[821314523] = { return Api.Update.parse_updateRecentEmojiStatuses($0) }
|
dict[821314523] = { return Api.Update.parse_updateRecentEmojiStatuses($0) }
|
||||||
dict[1870160884] = { return Api.Update.parse_updateRecentReactions($0) }
|
dict[1870160884] = { return Api.Update.parse_updateRecentReactions($0) }
|
||||||
|
@ -1165,7 +1165,7 @@ public extension Api {
|
|||||||
case updateReadFeaturedStickers
|
case updateReadFeaturedStickers
|
||||||
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
|
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
|
||||||
case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32)
|
case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32)
|
||||||
case updateReadMessagesContents(messages: [Int32], pts: Int32, ptsCount: Int32)
|
case updateReadMessagesContents(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?)
|
||||||
case updateReadStories(peer: Api.Peer, maxId: Int32)
|
case updateReadStories(peer: Api.Peer, maxId: Int32)
|
||||||
case updateRecentEmojiStatuses
|
case updateRecentEmojiStatuses
|
||||||
case updateRecentReactions
|
case updateRecentReactions
|
||||||
@ -2007,10 +2007,11 @@ public extension Api {
|
|||||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||||
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
case .updateReadMessagesContents(let messages, let pts, let ptsCount):
|
case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1757493555)
|
buffer.appendInt32(-131960447)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(messages.count))
|
buffer.appendInt32(Int32(messages.count))
|
||||||
for item in messages {
|
for item in messages {
|
||||||
@ -2018,6 +2019,7 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||||
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .updateReadStories(let peer, let maxId):
|
case .updateReadStories(let peer, let maxId):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -2384,8 +2386,8 @@ public extension Api {
|
|||||||
return ("updateReadHistoryInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
return ("updateReadHistoryInbox", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any), ("maxId", maxId as Any), ("stillUnreadCount", stillUnreadCount as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
||||||
case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount):
|
case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount):
|
||||||
return ("updateReadHistoryOutbox", [("peer", peer as Any), ("maxId", maxId as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
return ("updateReadHistoryOutbox", [("peer", peer as Any), ("maxId", maxId as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
||||||
case .updateReadMessagesContents(let messages, let pts, let ptsCount):
|
case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date):
|
||||||
return ("updateReadMessagesContents", [("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
return ("updateReadMessagesContents", [("flags", flags as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any)])
|
||||||
case .updateReadStories(let peer, let maxId):
|
case .updateReadStories(let peer, let maxId):
|
||||||
return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)])
|
return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)])
|
||||||
case .updateRecentEmojiStatuses:
|
case .updateRecentEmojiStatuses:
|
||||||
@ -4116,19 +4118,25 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? {
|
public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? {
|
||||||
var _1: [Int32]?
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: [Int32]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||||
}
|
}
|
||||||
var _2: Int32?
|
|
||||||
_2 = reader.readInt32()
|
|
||||||
var _3: Int32?
|
var _3: Int32?
|
||||||
_3 = reader.readInt32()
|
_3 = reader.readInt32()
|
||||||
|
var _4: Int32?
|
||||||
|
_4 = reader.readInt32()
|
||||||
|
var _5: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
if _c1 && _c2 && _c3 {
|
let _c4 = _4 != nil
|
||||||
return Api.Update.updateReadMessagesContents(messages: _1!, pts: _2!, ptsCount: _3!)
|
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.Update.updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -522,7 +522,7 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
indirect enum Chat: TypeConstructorDescription {
|
indirect enum Chat: TypeConstructorDescription {
|
||||||
case channel(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?, usernames: [Api.Username]?)
|
case channel(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?, usernames: [Api.Username]?, storiesMaxId: Int32?)
|
||||||
case channelForbidden(flags: Int32, id: Int64, accessHash: Int64, title: String, untilDate: Int32?)
|
case channelForbidden(flags: Int32, id: Int64, accessHash: Int64, title: String, untilDate: Int32?)
|
||||||
case chat(flags: Int32, id: Int64, title: String, photo: Api.ChatPhoto, participantsCount: Int32, date: Int32, version: Int32, migratedTo: Api.InputChannel?, adminRights: Api.ChatAdminRights?, defaultBannedRights: Api.ChatBannedRights?)
|
case chat(flags: Int32, id: Int64, title: String, photo: Api.ChatPhoto, participantsCount: Int32, date: Int32, version: Int32, migratedTo: Api.InputChannel?, adminRights: Api.ChatAdminRights?, defaultBannedRights: Api.ChatBannedRights?)
|
||||||
case chatEmpty(id: Int64)
|
case chatEmpty(id: Int64)
|
||||||
@ -530,9 +530,9 @@ public extension Api {
|
|||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames):
|
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-2094689180)
|
buffer.appendInt32(-1795845413)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(flags2, buffer: buffer, boxed: false)
|
serializeInt32(flags2, buffer: buffer, boxed: false)
|
||||||
@ -556,6 +556,7 @@ public extension Api {
|
|||||||
for item in usernames! {
|
for item in usernames! {
|
||||||
item.serialize(buffer, true)
|
item.serialize(buffer, true)
|
||||||
}}
|
}}
|
||||||
|
if Int(flags2) & Int(1 << 4) != 0 {serializeInt32(storiesMaxId!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
|
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -600,8 +601,8 @@ public extension Api {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames):
|
case .channel(let flags, let flags2, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount, let usernames, let storiesMaxId):
|
||||||
return ("channel", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any), ("date", date as Any), ("restrictionReason", restrictionReason as Any), ("adminRights", adminRights as Any), ("bannedRights", bannedRights as Any), ("defaultBannedRights", defaultBannedRights as Any), ("participantsCount", participantsCount as Any), ("usernames", usernames as Any)])
|
return ("channel", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("username", username as Any), ("photo", photo as Any), ("date", date as Any), ("restrictionReason", restrictionReason as Any), ("adminRights", adminRights as Any), ("bannedRights", bannedRights as Any), ("defaultBannedRights", defaultBannedRights as Any), ("participantsCount", participantsCount as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any)])
|
||||||
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
|
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
|
||||||
return ("channelForbidden", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("untilDate", untilDate as Any)])
|
return ("channelForbidden", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("title", title as Any), ("untilDate", untilDate as Any)])
|
||||||
case .chat(let flags, let id, let title, let photo, let participantsCount, let date, let version, let migratedTo, let adminRights, let defaultBannedRights):
|
case .chat(let flags, let id, let title, let photo, let participantsCount, let date, let version, let migratedTo, let adminRights, let defaultBannedRights):
|
||||||
@ -654,6 +655,8 @@ public extension Api {
|
|||||||
if Int(_2!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
if Int(_2!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
||||||
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self)
|
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self)
|
||||||
} }
|
} }
|
||||||
|
var _15: Int32?
|
||||||
|
if Int(_2!) & Int(1 << 4) != 0 {_15 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
@ -668,8 +671,9 @@ public extension Api {
|
|||||||
let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil
|
let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil
|
||||||
let _c13 = (Int(_1!) & Int(1 << 17) == 0) || _13 != nil
|
let _c13 = (Int(_1!) & Int(1 << 17) == 0) || _13 != nil
|
||||||
let _c14 = (Int(_2!) & Int(1 << 0) == 0) || _14 != nil
|
let _c14 = (Int(_2!) & Int(1 << 0) == 0) || _14 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
|
let _c15 = (Int(_2!) & Int(1 << 4) == 0) || _15 != nil
|
||||||
return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
|
||||||
|
return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -124,6 +124,7 @@ enum AccountStateMutationOperation {
|
|||||||
case UpdateReadStories(peerId: PeerId, maxId: Int32)
|
case UpdateReadStories(peerId: PeerId, maxId: Int32)
|
||||||
case UpdateStoryStealthMode(data: Api.StoriesStealthMode)
|
case UpdateStoryStealthMode(data: Api.StoriesStealthMode)
|
||||||
case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction)
|
case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction)
|
||||||
|
case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HoleFromPreviousState {
|
struct HoleFromPreviousState {
|
||||||
@ -467,7 +468,7 @@ struct AccountMutableState {
|
|||||||
|
|
||||||
for chat in chats {
|
for chat in chats {
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in
|
self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in
|
||||||
var previous: CachedChannelData
|
var previous: CachedChannelData
|
||||||
@ -653,9 +654,13 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdateStorySentReaction(peerId: peerId, id: id, reaction: reaction))
|
self.addOperation(.UpdateStorySentReaction(peerId: peerId, id: id, reaction: reaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func updateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String) {
|
||||||
|
self.addOperation(.UpdateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date, device: device, location: location))
|
||||||
|
}
|
||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization:
|
||||||
break
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
@ -61,7 +61,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
|||||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||||
case let .chatForbidden(id, title):
|
case let .chatForbidden(id, title):
|
||||||
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||||
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames):
|
case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames, _):
|
||||||
let isMin = (flags & (1 << 12)) != 0
|
let isMin = (flags & (1 << 12)) != 0
|
||||||
|
|
||||||
let participationStatus: TelegramChannelParticipationStatus
|
let participationStatus: TelegramChannelParticipationStatus
|
||||||
@ -170,7 +170,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
|||||||
switch rhs {
|
switch rhs {
|
||||||
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
||||||
return parseTelegramGroupOrChannel(chat: rhs)
|
return parseTelegramGroupOrChannel(chat: rhs)
|
||||||
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames):
|
case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames, _):
|
||||||
let isMin = (flags & (1 << 12)) != 0
|
let isMin = (flags & (1 << 12)) != 0
|
||||||
if accessHash != nil && !isMin {
|
if accessHash != nil && !isMin {
|
||||||
return parseTelegramGroupOrChannel(chat: rhs)
|
return parseTelegramGroupOrChannel(chat: rhs)
|
||||||
|
@ -18,6 +18,9 @@ public enum TelegramChannelPermission {
|
|||||||
case changeInfo
|
case changeInfo
|
||||||
case canBeAnonymous
|
case canBeAnonymous
|
||||||
case manageCalls
|
case manageCalls
|
||||||
|
case postStories
|
||||||
|
case editStories
|
||||||
|
case deleteStories
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension TelegramChannel {
|
public extension TelegramChannel {
|
||||||
@ -235,6 +238,24 @@ public extension TelegramChannel {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
case .postStories:
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canPostStories)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .editStories:
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canEditStories)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .deleteStories:
|
||||||
|
if let adminRights = self.adminRights {
|
||||||
|
return adminRights.rights.contains(.canDeleteStories)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1479,7 +1479,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
case let .updateChannelPinnedTopic(flags, channelId, topicId):
|
case let .updateChannelPinnedTopic(flags, channelId, topicId):
|
||||||
let isPinned = (flags & (1 << 0)) != 0
|
let isPinned = (flags & (1 << 0)) != 0
|
||||||
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
|
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
|
||||||
case let .updateReadMessagesContents(messages, _, _):
|
case let .updateReadMessagesContents(_, messages, _, _, _):
|
||||||
updatedState.addReadMessagesContents((nil, messages))
|
updatedState.addReadMessagesContents((nil, messages))
|
||||||
case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages):
|
case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages):
|
||||||
let _ = topMsgId
|
let _ = topMsgId
|
||||||
@ -1681,6 +1681,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
updatedState.updateStoryStealthMode(stealthMode)
|
updatedState.updateStoryStealthMode(stealthMode)
|
||||||
case let .updateSentStoryReaction(peerId, storyId, reaction):
|
case let .updateSentStoryReaction(peerId, storyId, reaction):
|
||||||
updatedState.updateStorySentReaction(peerId: peerId.peerId, id: storyId, reaction: reaction)
|
updatedState.updateStorySentReaction(peerId: peerId.peerId, id: storyId, reaction: reaction)
|
||||||
|
case let .updateNewAuthorization(flags, hash, date, device, location):
|
||||||
|
let isUnconfirmed = (flags & (1 << 0)) != 0
|
||||||
|
updatedState.updateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date ?? 0, device: device ?? "", location: location ?? "")
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -3169,7 +3172,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization:
|
||||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -4586,6 +4589,7 @@ func replayFinalState(
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: updatedReaction
|
myReaction: updatedReaction
|
||||||
))
|
))
|
||||||
if let entry = CodableEntry(updatedItem) {
|
if let entry = CodableEntry(updatedItem) {
|
||||||
@ -4616,6 +4620,7 @@ func replayFinalState(
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: MessageReaction.Reaction(apiReaction: reaction)
|
myReaction: MessageReaction.Reaction(apiReaction: reaction)
|
||||||
))
|
))
|
||||||
if let entry = CodableEntry(updatedItem) {
|
if let entry = CodableEntry(updatedItem) {
|
||||||
@ -4623,6 +4628,20 @@ func replayFinalState(
|
|||||||
storyUpdates.append(InternalStoryUpdate.added(peerId: peerId, item: updatedItem))
|
storyUpdates.append(InternalStoryUpdate.added(peerId: peerId, item: updatedItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .UpdateNewAuthorization(isUnconfirmed, hash, date, device, location):
|
||||||
|
let id = NewSessionReview.Id(id: hash)
|
||||||
|
if isUnconfirmed {
|
||||||
|
if let entry = CodableEntry(NewSessionReview(
|
||||||
|
id: hash,
|
||||||
|
device: device,
|
||||||
|
location: location,
|
||||||
|
timestamp: date
|
||||||
|
)) {
|
||||||
|
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, item: OrderedItemListEntry(id: id.rawValue, contents: entry), removeTailIfCountExceeds: 200)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: id.rawValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import MtProtoKit
|
|||||||
struct AccumulatedPeers {
|
struct AccumulatedPeers {
|
||||||
var peers: [PeerId: Peer] = [:]
|
var peers: [PeerId: Peer] = [:]
|
||||||
var users: [PeerId: Api.User] = [:]
|
var users: [PeerId: Api.User] = [:]
|
||||||
|
var chats: [PeerId: Api.Chat] = [:]
|
||||||
|
|
||||||
var allIds: Set<PeerId> {
|
var allIds: Set<PeerId> {
|
||||||
var result = Set<PeerId>()
|
var result = Set<PeerId>()
|
||||||
@ -31,6 +32,9 @@ struct AccumulatedPeers {
|
|||||||
for user in users {
|
for user in users {
|
||||||
self.users[user.peerId] = user
|
self.users[user.peerId] = user
|
||||||
}
|
}
|
||||||
|
for chat in chats {
|
||||||
|
self.chats[chat.peerId] = chat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(chats: [Api.Chat], users: [Api.User]) {
|
init(chats: [Api.Chat], users: [Api.User]) {
|
||||||
@ -42,6 +46,9 @@ struct AccumulatedPeers {
|
|||||||
for user in users {
|
for user in users {
|
||||||
self.users[user.peerId] = user
|
self.users[user.peerId] = user
|
||||||
}
|
}
|
||||||
|
for chat in chats {
|
||||||
|
self.chats[chat.peerId] = chat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(users: [Api.User]) {
|
init(users: [Api.User]) {
|
||||||
@ -65,6 +72,9 @@ struct AccumulatedPeers {
|
|||||||
for (id, user) in other.users {
|
for (id, user) in other.users {
|
||||||
result.users[id] = user
|
result.users[id] = user
|
||||||
}
|
}
|
||||||
|
for (id, chat) in other.chats {
|
||||||
|
result.chats[id] = chat
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func apiUpdatePtsRange(_ update: Api.Update) -> (Int32, Int32)? {
|
|||||||
return (pts, ptsCount)
|
return (pts, ptsCount)
|
||||||
case let .updateEditMessage(_, pts, ptsCount):
|
case let .updateEditMessage(_, pts, ptsCount):
|
||||||
return (pts, ptsCount)
|
return (pts, ptsCount)
|
||||||
case let .updateReadMessagesContents(_, pts, ptsCount):
|
case let .updateReadMessagesContents(_, _, pts, ptsCount, _):
|
||||||
return (pts, ptsCount)
|
return (pts, ptsCount)
|
||||||
case let .updateWebPage(_, pts, ptsCount):
|
case let .updateWebPage(_, pts, ptsCount):
|
||||||
return (pts, ptsCount)
|
return (pts, ptsCount)
|
||||||
|
@ -181,7 +181,7 @@ extension Api.Chat {
|
|||||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
|
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
|
||||||
case let .chatForbidden(id, _):
|
case let .chatForbidden(id, _):
|
||||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
|
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
|
||||||
case let .channel(_, _, id, _, _, _, _, _, _, _, _, _, _, _):
|
case let .channel(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
|
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
|
||||||
case let .channelForbidden(_, id, _, _, _):
|
case let .channelForbidden(_, id, _, _, _):
|
||||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
|
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import TelegramApi
|
||||||
|
|
||||||
public final class NewSessionReview: Codable, Equatable {
|
public final class NewSessionReview: Codable, Equatable {
|
||||||
struct Id {
|
struct Id {
|
||||||
@ -19,11 +20,13 @@ public final class NewSessionReview: Codable, Equatable {
|
|||||||
public let id: Int64
|
public let id: Int64
|
||||||
public let device: String
|
public let device: String
|
||||||
public let location: String
|
public let location: String
|
||||||
|
public let timestamp: Int32
|
||||||
|
|
||||||
public init(id: Int64, device: String, location: String) {
|
public init(id: Int64, device: String, location: String, timestamp: Int32) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.device = device
|
self.device = device
|
||||||
self.location = location
|
self.location = location
|
||||||
|
self.timestamp = timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -32,6 +35,7 @@ public final class NewSessionReview: Codable, Equatable {
|
|||||||
self.id = try container.decode(Int64.self, forKey: "id")
|
self.id = try container.decode(Int64.self, forKey: "id")
|
||||||
self.device = try container.decode(String.self, forKey: "device")
|
self.device = try container.decode(String.self, forKey: "device")
|
||||||
self.location = try container.decode(String.self, forKey: "location")
|
self.location = try container.decode(String.self, forKey: "location")
|
||||||
|
self.timestamp = try container.decode(Int32.self, forKey: "timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -40,6 +44,7 @@ public final class NewSessionReview: Codable, Equatable {
|
|||||||
try container.encode(self.id, forKey: "id")
|
try container.encode(self.id, forKey: "id")
|
||||||
try container.encode(self.device, forKey: "device")
|
try container.encode(self.device, forKey: "device")
|
||||||
try container.encode(self.location, forKey: "location")
|
try container.encode(self.location, forKey: "location")
|
||||||
|
try container.encode(self.timestamp, forKey: "timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: NewSessionReview, rhs: NewSessionReview) -> Bool {
|
public static func ==(lhs: NewSessionReview, rhs: NewSessionReview) -> Bool {
|
||||||
@ -52,10 +57,42 @@ public final class NewSessionReview: Codable, Equatable {
|
|||||||
if lhs.location != rhs.location {
|
if lhs.location != rhs.location {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.timestamp != rhs.timestamp {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_cleanupSessionReviews(account: Account) -> Signal<Never, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|
var autoconfirmTimeout: Int32 = 7 * 24 * 60 * 60
|
||||||
|
let appConfig = currentAppConfiguration(transaction: transaction)
|
||||||
|
if let data = appConfig.data {
|
||||||
|
if let value = data["authorization_autoconfirm_period"] as? Double {
|
||||||
|
autoconfirmTimeout = Int32(round(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||||
|
var removeIds: [MemoryBuffer] = []
|
||||||
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.NewSessionReviews) {
|
||||||
|
guard let item = entry.contents.get(NewSessionReview.self) else {
|
||||||
|
removeIds.append(entry.id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if item.timestamp <= timestamp - autoconfirmTimeout {
|
||||||
|
removeIds.append(entry.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for removeId in removeIds {
|
||||||
|
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: removeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
public func newSessionReviews(postbox: Postbox) -> Signal<[NewSessionReview], NoError> {
|
public func newSessionReviews(postbox: Postbox) -> Signal<[NewSessionReview], NoError> {
|
||||||
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.NewSessionReviews)
|
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.NewSessionReviews)
|
||||||
return postbox.combinedView(keys: [viewKey])
|
return postbox.combinedView(keys: [viewKey])
|
||||||
@ -102,3 +139,11 @@ public func removeNewSessionReviews(postbox: Postbox, ids: [Int64]) -> Signal<Ne
|
|||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_confirmNewSessionReview(account: Account, id: Int64) -> Signal<Never, NoError> {
|
||||||
|
return account.network.request(Api.functions.account.changeAuthorizationSettings(flags: 1 << 3, hash: id, encryptedRequestsDisabled: nil, callRequestsDisabled: nil))
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
|
return .single(.boolFalse)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
@ -22,13 +22,17 @@ public struct TelegramChatAdminRightsFlags: OptionSet, Hashable {
|
|||||||
public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10)
|
public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10)
|
||||||
public static let canManageCalls = TelegramChatAdminRightsFlags(rawValue: 1 << 11)
|
public static let canManageCalls = TelegramChatAdminRightsFlags(rawValue: 1 << 11)
|
||||||
public static let canManageTopics = TelegramChatAdminRightsFlags(rawValue: 1 << 13)
|
public static let canManageTopics = TelegramChatAdminRightsFlags(rawValue: 1 << 13)
|
||||||
|
public static let canPostStories = TelegramChatAdminRightsFlags(rawValue: 1 << 14)
|
||||||
|
public static let canEditStories = TelegramChatAdminRightsFlags(rawValue: 1 << 15)
|
||||||
|
public static let canDeleteStories = TelegramChatAdminRightsFlags(rawValue: 1 << 16)
|
||||||
|
|
||||||
|
|
||||||
public static var all: TelegramChatAdminRightsFlags {
|
public static var all: TelegramChatAdminRightsFlags {
|
||||||
return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canBeAnonymous, .canManageCalls, .canManageTopics]
|
return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canBeAnonymous, .canManageCalls, .canManageTopics, .canPostStories, .canEditStories, .canDeleteStories]
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var allChannel: TelegramChatAdminRightsFlags {
|
public static var allChannel: TelegramChatAdminRightsFlags {
|
||||||
return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canManageCalls, .canManageTopics]
|
return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canManageCalls, .canManageTopics, .canPostStories, .canEditStories, .canDeleteStories]
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let internal_groupSpecific: TelegramChatAdminRightsFlags = [
|
public static let internal_groupSpecific: TelegramChatAdminRightsFlags = [
|
||||||
@ -49,7 +53,10 @@ public struct TelegramChatAdminRightsFlags: OptionSet, Hashable {
|
|||||||
.canDeleteMessages,
|
.canDeleteMessages,
|
||||||
.canManageCalls,
|
.canManageCalls,
|
||||||
.canInviteUsers,
|
.canInviteUsers,
|
||||||
.canAddAdmins
|
.canAddAdmins,
|
||||||
|
.canPostStories,
|
||||||
|
.canEditStories,
|
||||||
|
.canDeleteStories
|
||||||
]
|
]
|
||||||
|
|
||||||
public static func peerSpecific(peer: EnginePeer) -> TelegramChatAdminRightsFlags {
|
public static func peerSpecific(peer: EnginePeer) -> TelegramChatAdminRightsFlags {
|
||||||
|
@ -2254,7 +2254,7 @@ func _internal_groupCallDisplayAsAvailablePeers(accountPeerId: PeerId, network:
|
|||||||
for chat in chats {
|
for chat in chats {
|
||||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
subscribers[groupOrChannel.id] = participantsCount
|
subscribers[groupOrChannel.id] = participantsCount
|
||||||
}
|
}
|
||||||
|
@ -375,6 +375,7 @@ public final class EngineStoryViewListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
))
|
))
|
||||||
if let entry = CodableEntry(updatedItem) {
|
if let entry = CodableEntry(updatedItem) {
|
||||||
@ -411,6 +412,7 @@ public final class EngineStoryViewListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
))
|
))
|
||||||
if let entry = CodableEntry(updatedItem) {
|
if let entry = CodableEntry(updatedItem) {
|
||||||
|
@ -138,7 +138,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
|
|||||||
for chat in chats {
|
for chat in chats {
|
||||||
if let groupOrChannel = parsedPeers.get(chat.peerId) {
|
if let groupOrChannel = parsedPeers.get(chat.peerId) {
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
subscribers[groupOrChannel.id] = participantsCount
|
subscribers[groupOrChannel.id] = participantsCount
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,7 @@ public enum Stories {
|
|||||||
case isSelectedContacts
|
case isSelectedContacts
|
||||||
case isForwardingDisabled
|
case isForwardingDisabled
|
||||||
case isEdited
|
case isEdited
|
||||||
|
case isMy
|
||||||
case myReaction
|
case myReaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +201,7 @@ public enum Stories {
|
|||||||
public let isSelectedContacts: Bool
|
public let isSelectedContacts: Bool
|
||||||
public let isForwardingDisabled: Bool
|
public let isForwardingDisabled: Bool
|
||||||
public let isEdited: Bool
|
public let isEdited: Bool
|
||||||
|
public let isMy: Bool
|
||||||
public let myReaction: MessageReaction.Reaction?
|
public let myReaction: MessageReaction.Reaction?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -220,6 +222,7 @@ public enum Stories {
|
|||||||
isSelectedContacts: Bool,
|
isSelectedContacts: Bool,
|
||||||
isForwardingDisabled: Bool,
|
isForwardingDisabled: Bool,
|
||||||
isEdited: Bool,
|
isEdited: Bool,
|
||||||
|
isMy: Bool,
|
||||||
myReaction: MessageReaction.Reaction?
|
myReaction: MessageReaction.Reaction?
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -239,6 +242,7 @@ public enum Stories {
|
|||||||
self.isSelectedContacts = isSelectedContacts
|
self.isSelectedContacts = isSelectedContacts
|
||||||
self.isForwardingDisabled = isForwardingDisabled
|
self.isForwardingDisabled = isForwardingDisabled
|
||||||
self.isEdited = isEdited
|
self.isEdited = isEdited
|
||||||
|
self.isMy = isMy
|
||||||
self.myReaction = myReaction
|
self.myReaction = myReaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,6 +272,7 @@ public enum Stories {
|
|||||||
self.isSelectedContacts = try container.decodeIfPresent(Bool.self, forKey: .isSelectedContacts) ?? false
|
self.isSelectedContacts = try container.decodeIfPresent(Bool.self, forKey: .isSelectedContacts) ?? false
|
||||||
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
|
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
|
||||||
self.isEdited = try container.decodeIfPresent(Bool.self, forKey: .isEdited) ?? false
|
self.isEdited = try container.decodeIfPresent(Bool.self, forKey: .isEdited) ?? false
|
||||||
|
self.isMy = try container.decodeIfPresent(Bool.self, forKey: .isMy) ?? false
|
||||||
self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction)
|
self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,6 +303,7 @@ public enum Stories {
|
|||||||
try container.encode(self.isSelectedContacts, forKey: .isSelectedContacts)
|
try container.encode(self.isSelectedContacts, forKey: .isSelectedContacts)
|
||||||
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
|
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
|
||||||
try container.encode(self.isEdited, forKey: .isEdited)
|
try container.encode(self.isEdited, forKey: .isEdited)
|
||||||
|
try container.encode(self.isMy, forKey: .isMy)
|
||||||
try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
|
try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1025,6 +1031,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||||
@ -1204,6 +1211,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||||
@ -1232,6 +1240,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||||
@ -1406,6 +1415,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||||
@ -1433,6 +1443,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
updatedItems.append(updatedItem)
|
updatedItems.append(updatedItem)
|
||||||
@ -1575,6 +1586,13 @@ extension Stories.StoredItem {
|
|||||||
mergedMyReaction = sentReaction.flatMap(MessageReaction.Reaction.init(apiReaction:))
|
mergedMyReaction = sentReaction.flatMap(MessageReaction.Reaction.init(apiReaction:))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mergedIsMy: Bool
|
||||||
|
if isMin, let existingItem = existingItem {
|
||||||
|
mergedIsMy = existingItem.isMy
|
||||||
|
} else {
|
||||||
|
mergedIsMy = (flags & (1 << 16)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
let item = Stories.Item(
|
let item = Stories.Item(
|
||||||
id: id,
|
id: id,
|
||||||
timestamp: date,
|
timestamp: date,
|
||||||
@ -1593,6 +1611,7 @@ extension Stories.StoredItem {
|
|||||||
isSelectedContacts: isSelectedContacts,
|
isSelectedContacts: isSelectedContacts,
|
||||||
isForwardingDisabled: isForwardingDisabled,
|
isForwardingDisabled: isForwardingDisabled,
|
||||||
isEdited: isEdited,
|
isEdited: isEdited,
|
||||||
|
isMy: mergedIsMy,
|
||||||
myReaction: mergedMyReaction
|
myReaction: mergedMyReaction
|
||||||
)
|
)
|
||||||
self = .item(item)
|
self = .item(item)
|
||||||
@ -2046,6 +2065,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: reaction
|
myReaction: reaction
|
||||||
))
|
))
|
||||||
updatedItemValue = updatedItem
|
updatedItemValue = updatedItem
|
||||||
@ -2076,6 +2096,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: reaction
|
myReaction: reaction
|
||||||
))
|
))
|
||||||
updatedItemValue = updatedItem
|
updatedItemValue = updatedItem
|
||||||
|
@ -69,9 +69,10 @@ public final class EngineStoryItem: Equatable {
|
|||||||
public let isSelectedContacts: Bool
|
public let isSelectedContacts: Bool
|
||||||
public let isForwardingDisabled: Bool
|
public let isForwardingDisabled: Bool
|
||||||
public let isEdited: Bool
|
public let isEdited: Bool
|
||||||
|
public let isMy: Bool
|
||||||
public let myReaction: MessageReaction.Reaction?
|
public let myReaction: MessageReaction.Reaction?
|
||||||
|
|
||||||
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, myReaction: MessageReaction.Reaction?) {
|
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.expirationTimestamp = expirationTimestamp
|
self.expirationTimestamp = expirationTimestamp
|
||||||
@ -90,6 +91,7 @@ public final class EngineStoryItem: Equatable {
|
|||||||
self.isSelectedContacts = isSelectedContacts
|
self.isSelectedContacts = isSelectedContacts
|
||||||
self.isForwardingDisabled = isForwardingDisabled
|
self.isForwardingDisabled = isForwardingDisabled
|
||||||
self.isEdited = isEdited
|
self.isEdited = isEdited
|
||||||
|
self.isMy = isMy
|
||||||
self.myReaction = myReaction
|
self.myReaction = myReaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +150,9 @@ public final class EngineStoryItem: Equatable {
|
|||||||
if lhs.isEdited != rhs.isEdited {
|
if lhs.isEdited != rhs.isEdited {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isMy != rhs.isMy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.myReaction != rhs.myReaction {
|
if lhs.myReaction != rhs.myReaction {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -189,6 +194,7 @@ extension EngineStoryItem {
|
|||||||
isSelectedContacts: self.isSelectedContacts,
|
isSelectedContacts: self.isSelectedContacts,
|
||||||
isForwardingDisabled: self.isForwardingDisabled,
|
isForwardingDisabled: self.isForwardingDisabled,
|
||||||
isEdited: self.isEdited,
|
isEdited: self.isEdited,
|
||||||
|
isMy: self.isMy,
|
||||||
myReaction: self.myReaction
|
myReaction: self.myReaction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -563,6 +569,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
items.append(mappedItem)
|
items.append(mappedItem)
|
||||||
@ -693,6 +700,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
storyItems.append(mappedItem)
|
storyItems.append(mappedItem)
|
||||||
@ -847,6 +855,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
finalUpdatedState = updatedState
|
finalUpdatedState = updatedState
|
||||||
@ -892,6 +901,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
finalUpdatedState = updatedState
|
finalUpdatedState = updatedState
|
||||||
@ -939,6 +949,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
))
|
))
|
||||||
updatedState.items.sort(by: { lhs, rhs in
|
updatedState.items.sort(by: { lhs, rhs in
|
||||||
@ -982,6 +993,7 @@ public final class PeerStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
))
|
))
|
||||||
updatedState.items.sort(by: { lhs, rhs in
|
updatedState.items.sort(by: { lhs, rhs in
|
||||||
@ -1149,6 +1161,7 @@ public final class PeerExpiringStoryListContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
items.append(.item(mappedItem))
|
items.append(.item(mappedItem))
|
||||||
|
@ -1132,6 +1132,7 @@ public extension TelegramEngine {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
))
|
))
|
||||||
if let entry = CodableEntry(updatedItem) {
|
if let entry = CodableEntry(updatedItem) {
|
||||||
|
@ -280,7 +280,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
var memberCounts: [PeerId: Int] = [:]
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
|
||||||
for chat in chats {
|
for chat in chats {
|
||||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
memberCounts[chat.peerId] = Int(participantsCount)
|
memberCounts[chat.peerId] = Int(participantsCount)
|
||||||
}
|
}
|
||||||
@ -307,7 +307,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
var memberCounts: [PeerId: Int] = [:]
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
|
||||||
for chat in chats {
|
for chat in chats {
|
||||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
memberCounts[chat.peerId] = Int(participantsCount)
|
memberCounts[chat.peerId] = Int(participantsCount)
|
||||||
}
|
}
|
||||||
@ -621,7 +621,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
|||||||
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
|
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
|
||||||
|
|
||||||
for chat in chats {
|
for chat in chats {
|
||||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
memberCounts.append(ChatListFiltersState.ChatListFilterUpdates.MemberCount(id: chat.peerId, count: participantsCount))
|
memberCounts.append(ChatListFiltersState.ChatListFilterUpdates.MemberCount(id: chat.peerId, count: participantsCount))
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func _internal_inactiveChannelList(network: Network) -> Signal<[InactiveChannel]
|
|||||||
var participantsCounts: [PeerId: Int32] = [:]
|
var participantsCounts: [PeerId: Int32] = [:]
|
||||||
for chat in chats {
|
for chat in chats {
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _):
|
||||||
if let participantsCountValue = participantsCountValue {
|
if let participantsCountValue = participantsCountValue {
|
||||||
participantsCounts[chat.peerId] = participantsCountValue
|
participantsCounts[chat.peerId] = participantsCountValue
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo
|
|||||||
for chat in chats {
|
for chat in chats {
|
||||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||||
if let participantsCount = participantsCount {
|
if let participantsCount = participantsCount {
|
||||||
subscribers[groupOrChannel.id] = participantsCount
|
subscribers[groupOrChannel.id] = participantsCount
|
||||||
}
|
}
|
||||||
|
@ -820,7 +820,7 @@ public extension TelegramEngine {
|
|||||||
|> beforeNext { _ in
|
|> beforeNext { _ in
|
||||||
let delayTime = CFAbsoluteTimeGetCurrent() - startTime
|
let delayTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
if delayTime > 0.3 {
|
if delayTime > 0.3 {
|
||||||
Logger.shared.log("getNextUnreadChannel", "took \(delayTime) s")
|
//Logger.shared.log("getNextUnreadChannel", "took \(delayTime) s")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,5 +68,21 @@ public extension TelegramEngine {
|
|||||||
public func updateCloseFriends(peerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
public func updateCloseFriends(peerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
||||||
return _internal_updateCloseFriends(account: self.account, peerIds: peerIds)
|
return _internal_updateCloseFriends(account: self.account, peerIds: peerIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cleanupSessionReviews() -> Signal<Never, NoError> {
|
||||||
|
return _internal_cleanupSessionReviews(account: self.account)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func confirmNewSessionReview(id: Int64) -> Signal<Never, NoError> {
|
||||||
|
let _ = removeNewSessionReviews(postbox: self.account.postbox, ids: [id]).start()
|
||||||
|
return _internal_confirmNewSessionReview(account: self.account, id: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func terminateAnotherSession(id: Int64) -> Signal<Never, TerminateSessionError> {
|
||||||
|
let _ = removeNewSessionReviews(postbox: self.account.postbox, ids: [id]).start()
|
||||||
|
|
||||||
|
return terminateAccountSession(account: self.account, hash: id)
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
|
|||||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
var participantCount: Int32? = nil
|
var participantCount: Int32? = nil
|
||||||
switch chat {
|
switch chat {
|
||||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _):
|
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _):
|
||||||
participantCount = participantsCountValue
|
participantCount = participantsCountValue
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -70,6 +70,21 @@ func updatePeers(transaction: Transaction, accountPeerId: PeerId, peers: Accumul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (_, chat) in peers.chats {
|
||||||
|
switch chat {
|
||||||
|
case let .channel(flags, flags2, _, _, _, _, _, _, _, _, _, _, _, _, storiesMaxId):
|
||||||
|
let isMin = (flags & (1 << 12)) != 0
|
||||||
|
let storiesUnavailable = (flags2 & (1 << 3)) != 0
|
||||||
|
|
||||||
|
if let storiesMaxId = storiesMaxId {
|
||||||
|
transaction.setStoryItemsInexactMaxId(peerId: chat.peerId, id: storiesMaxId)
|
||||||
|
} else if !isMin && storiesUnavailable {
|
||||||
|
transaction.clearStoryItemsInexactMaxId(peerId: chat.peerId)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
for (_, peer) in peers.peers {
|
for (_, peer) in peers.peers {
|
||||||
parsedPeers.append(peer)
|
parsedPeers.append(peer)
|
||||||
}
|
}
|
||||||
|
@ -2809,7 +2809,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
if let accountPeer {
|
if let accountPeer {
|
||||||
sendAsPeers.append(accountPeer)
|
sendAsPeers.append(accountPeer)
|
||||||
}
|
}
|
||||||
sendAsPeers.append(contentsOf: adminedChannels)
|
for channel in adminedChannels {
|
||||||
|
if case let .channel(channel) = channel, channel.hasPermission(.postStories) {
|
||||||
|
sendAsPeers.append(contentsOf: adminedChannels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers
|
let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers
|
||||||
var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:]
|
var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:]
|
||||||
|
@ -3241,6 +3241,10 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case .separator = subItems.last {
|
||||||
|
subItems.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
if let sourceLabelView = sourceView.labelView {
|
if let sourceLabelView = sourceView.labelView {
|
||||||
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
||||||
let source: ContextContentSource = .reference(StorageUsageContextReferenceContentSource(sourceView: sourceLabelView))
|
let source: ContextContentSource = .reference(StorageUsageContextReferenceContentSource(sourceView: sourceLabelView))
|
||||||
|
@ -187,6 +187,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
isSelectedContacts: item.isSelectedContacts,
|
isSelectedContacts: item.isSelectedContacts,
|
||||||
isForwardingDisabled: item.isForwardingDisabled,
|
isForwardingDisabled: item.isForwardingDisabled,
|
||||||
isEdited: item.isEdited,
|
isEdited: item.isEdited,
|
||||||
|
isMy: item.isMy,
|
||||||
myReaction: item.myReaction
|
myReaction: item.myReaction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -220,6 +221,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
isSelectedContacts: item.privacy.base == .nobody,
|
isSelectedContacts: item.privacy.base == .nobody,
|
||||||
isForwardingDisabled: false,
|
isForwardingDisabled: false,
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
|
isMy: true,
|
||||||
myReaction: nil
|
myReaction: nil
|
||||||
))
|
))
|
||||||
totalCount += 1
|
totalCount += 1
|
||||||
@ -1096,6 +1098,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||||||
isSelectedContacts: itemValue.isSelectedContacts,
|
isSelectedContacts: itemValue.isSelectedContacts,
|
||||||
isForwardingDisabled: itemValue.isForwardingDisabled,
|
isForwardingDisabled: itemValue.isForwardingDisabled,
|
||||||
isEdited: itemValue.isEdited,
|
isEdited: itemValue.isEdited,
|
||||||
|
isMy: itemValue.isMy,
|
||||||
myReaction: itemValue.myReaction
|
myReaction: itemValue.myReaction
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5679,59 +5679,68 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
guard let component = self.component, let controller = component.controller() else {
|
guard let component = self.component, let controller = component.controller() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard case let .channel(channel) = component.slice.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
||||||
}, action: { [weak self] _, a in
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||||
a(.default)
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
guard let self else {
|
|
||||||
return
|
guard let self else {
|
||||||
}
|
return
|
||||||
self.openStoryEditing()
|
}
|
||||||
})))
|
self.openStoryEditing()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
items.append(.separator)
|
if !items.isEmpty {
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:localize
|
if channel.hasPermission(.editStories) {
|
||||||
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from Posts" : "Save to Posts", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start()
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if component.slice.item.storyItem.isPinned {
|
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from Posts" : "Save to Posts", icon: { theme in
|
||||||
self.component?.presentController(UndoOverlayController(
|
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor)
|
||||||
presentationData: presentationData,
|
}, action: { [weak self] _, a in
|
||||||
content: .info(title: nil, text: "Story removed from the channel's profile", timeout: nil),
|
a(.default)
|
||||||
elevatedLayout: false,
|
|
||||||
animateInAsReplacement: false,
|
guard let self, let component = self.component else {
|
||||||
blurred: true,
|
return
|
||||||
action: { _ in return false }
|
}
|
||||||
), nil)
|
|
||||||
} else {
|
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start()
|
||||||
self.component?.presentController(UndoOverlayController(
|
|
||||||
presentationData: presentationData,
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
content: .info(title: "Story saved to the channel's profile", text: "Saved stories can be viewed by others on the channel's profile until an admin removes them.", timeout: nil),
|
//TODO:localize
|
||||||
elevatedLayout: false,
|
if component.slice.item.storyItem.isPinned {
|
||||||
animateInAsReplacement: false,
|
self.component?.presentController(UndoOverlayController(
|
||||||
blurred: true,
|
presentationData: presentationData,
|
||||||
action: { _ in return false }
|
content: .info(title: nil, text: "Story removed from the channel's profile", timeout: nil),
|
||||||
), nil)
|
elevatedLayout: false,
|
||||||
}
|
animateInAsReplacement: false,
|
||||||
})))
|
blurred: true,
|
||||||
|
action: { _ in return false }
|
||||||
|
), nil)
|
||||||
|
} else {
|
||||||
|
self.component?.presentController(UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .info(title: "Story saved to the channel's profile", text: "Saved stories can be viewed by others on the channel's profile until an admin removes them.", timeout: nil),
|
||||||
|
elevatedLayout: false,
|
||||||
|
animateInAsReplacement: false,
|
||||||
|
blurred: true,
|
||||||
|
action: { _ in return false }
|
||||||
|
), nil)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
let saveText: String = component.strings.Story_Context_SaveToGallery
|
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||||
@ -5745,23 +5754,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.requestSave()
|
self.requestSave()
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if case let .user(accountUser) = component.slice.peer {
|
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if accountUser.isPremium {
|
|
||||||
self.sendMessageContext.requestStealthMode(view: self)
|
|
||||||
} else {
|
|
||||||
self.presentStealthModeUpgradeScreen()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) {
|
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) {
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
@ -5773,7 +5765,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)
|
let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] link in
|
|> deliverOnMainQueue).start(next: { [weak self] link in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -5803,48 +5795,50 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
||||||
}, action: { [weak self] _, a in
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
a(.default)
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
guard let self, let component = self.component else {
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
|
||||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
|
||||||
|
|
||||||
actionSheet.setItemGroups([
|
|
||||||
ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.delete()
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
|
|
||||||
actionSheet.dismissed = { [weak self] _ in
|
|
||||||
guard let self else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.sendMessageContext.actionSheet = nil
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
|
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||||
|
|
||||||
|
actionSheet.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.delete()
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
actionSheet.dismissed = { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.sendMessageContext.actionSheet = nil
|
||||||
|
self.updateIsProgressPaused()
|
||||||
|
}
|
||||||
|
self.sendMessageContext.actionSheet = actionSheet
|
||||||
self.updateIsProgressPaused()
|
self.updateIsProgressPaused()
|
||||||
}
|
|
||||||
self.sendMessageContext.actionSheet = actionSheet
|
component.presentController(actionSheet, nil)
|
||||||
self.updateIsProgressPaused()
|
})))
|
||||||
|
}
|
||||||
component.presentController(actionSheet, nil)
|
|
||||||
})))
|
|
||||||
|
|
||||||
let (tip, tipSignal) = self.getLinkedStickerPacks()
|
let (tip, tipSignal) = self.getLinkedStickerPacks()
|
||||||
|
|
||||||
|
@ -5321,10 +5321,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
|
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
|
||||||
if channel.hasPermission(.sendSomething) {
|
if channel.hasPermission(.editStories) {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
items.append(.action(ContextMenuActionItem(text: "Archived Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: "Archived Stories", icon: { theme in
|
||||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor)
|
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user