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 {
|
||||
private let context: AccountContext
|
||||
private weak var controller: ChatListControllerImpl?
|
||||
|
@ -2345,19 +2345,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
for item in items {
|
||||
switch item {
|
||||
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _, _):
|
||||
if case .user = peer {
|
||||
storyStatsIds.append(peer.id)
|
||||
}
|
||||
storyStatsIds.append(peer.id)
|
||||
case let .localPeer(peer, _, _, _, _, _, _, _, _, _):
|
||||
if case .user = peer {
|
||||
storyStatsIds.append(peer.id)
|
||||
}
|
||||
storyStatsIds.append(peer.id)
|
||||
case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _):
|
||||
if foundPeer.peer is TelegramUser {
|
||||
storyStatsIds.append(foundPeer.peer.id)
|
||||
}
|
||||
storyStatsIds.append(foundPeer.peer.id)
|
||||
case let .message(_, peer, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let peer = peer.peer, case .user = peer {
|
||||
if let peer = peer.peer {
|
||||
storyStatsIds.append(peer.id)
|
||||
}
|
||||
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 currentForegroundColor: UIColor?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
@ -3504,13 +3498,13 @@ private final class ShimmerEffectNode: ASDisplayNode {
|
||||
|
||||
public final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
private let backgroundColorNode: ASDisplayNode
|
||||
private let effectNode: ShimmerEffectNode
|
||||
private let effectNode: SearchShimmerEffectNode
|
||||
private let maskNode: ASImageNode
|
||||
private var currentParams: (size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey)?
|
||||
|
||||
public init(key: ChatListSearchPaneKey) {
|
||||
self.backgroundColorNode = ASDisplayNode()
|
||||
self.effectNode = ShimmerEffectNode()
|
||||
self.effectNode = SearchShimmerEffectNode()
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
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 NewSessionInfoScreen
|
||||
|
||||
private var debugDidAddNewSessionReview = false
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList(appendContacts: 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
|
||||
}))
|
||||
|
||||
let _ = removeNewSessionReviews(postbox: self.context.account.postbox, ids: [newSessionReview.id]).start()
|
||||
let _ = self.context.engine.privacy.confirmNewSessionReview(id: newSessionReview.id)
|
||||
} else {
|
||||
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
|
||||
guard let self else {
|
||||
@ -1743,12 +1741,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let suggestedChatListNotice: Signal<ChatListNotice?, NoError>
|
||||
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
#if DEBUG
|
||||
if !debugDidAddNewSessionReview {
|
||||
debugDidAddNewSessionReview = true
|
||||
let _ = addNewSessionReview(postbox: context.account.postbox, item: NewSessionReview(id: 1, device: "iPhone 14 Pro", location: "Dubai, UAE")).start()
|
||||
}
|
||||
#endif
|
||||
let _ = context.engine.privacy.cleanupSessionReviews().start()
|
||||
|
||||
suggestedChatListNotice = .single(nil)
|
||||
|> 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.resetFilter()
|
||||
|
||||
|
@ -112,12 +112,12 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
self.contentContainer.clipsToBounds = true
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.contentContainer.addSubnode(self.titleNode)
|
||||
self.contentContainer.addSubnode(self.textNode)
|
||||
self.contentContainer.addSubnode(self.arrowNode)
|
||||
|
||||
self.addSubnode(self.contentContainer)
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
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 insets = UIEdgeInsets()
|
||||
public final var visualInsets: UIEdgeInsets?
|
||||
public final var dynamicVisualInsets: (() -> UIEdgeInsets)?
|
||||
public private(set) final var headerInsets = UIEdgeInsets()
|
||||
public private(set) final var scrollIndicatorInsets = UIEdgeInsets()
|
||||
private final var ensureTopInsetForOverlayHighlightedItems: CGFloat?
|
||||
@ -4388,7 +4389,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
if abs(apparentHeightDelta) > CGFloat.ulpOfOne {
|
||||
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 {
|
||||
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
|
||||
|
@ -20,7 +20,7 @@ private let rankMaxLength: Int32 = 16
|
||||
private final class ChannelAdminControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateAdminRights: (Bool) -> Void
|
||||
let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
||||
let toggleRight: (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void
|
||||
let toggleRightWhileDisabled: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
||||
let transferOwnership: () -> Void
|
||||
let updateRank: (String, String) -> Void
|
||||
@ -28,8 +28,9 @@ private final class ChannelAdminControllerArguments {
|
||||
let dismissAdmin: () -> Void
|
||||
let dismissInput: () -> 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.updateAdminRights = updateAdminRights
|
||||
self.toggleRight = toggleRight
|
||||
@ -40,6 +41,7 @@ private final class ChannelAdminControllerArguments {
|
||||
self.dismissAdmin = dismissAdmin
|
||||
self.dismissInput = dismissInput
|
||||
self.animateError = animateError
|
||||
self.toggleIsOptionExpanded = toggleIsOptionExpanded
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,12 +73,41 @@ private enum ChannelAdminEntryStableId: Hashable {
|
||||
case rankInfo
|
||||
case adminRights
|
||||
case rightsTitle
|
||||
case right(TelegramChatAdminRightsFlags)
|
||||
case right(RightsItem)
|
||||
case addAdminsInfo
|
||||
case transfer
|
||||
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 {
|
||||
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, EnginePeer.Presence?)
|
||||
case rankTitle(PresentationTheme, String, Int32?, Int32)
|
||||
@ -84,7 +115,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
case rankInfo(PresentationTheme, String, Bool)
|
||||
case adminRights(PresentationTheme, String, Bool)
|
||||
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 transfer(PresentationTheme, String)
|
||||
case dismiss(PresentationTheme, String)
|
||||
@ -120,7 +151,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
return .adminRights
|
||||
case .rightsTitle:
|
||||
return .rightsTitle
|
||||
case let .rightItem(_, _, _, right, _, _, _):
|
||||
case let .rightItem(_, _, _, right, _, _, _, _, _):
|
||||
return .right(right)
|
||||
case .addAdminsInfo:
|
||||
return .addAdminsInfo
|
||||
@ -185,8 +216,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .rightItem(lhsTheme, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled):
|
||||
if case let .rightItem(rhsTheme, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled) = rhs {
|
||||
case let .rightItem(lhsTheme, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled, lhsSubItems, lhsIsExpanded):
|
||||
if case let .rightItem(rhsTheme, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled, rhsSubItems, rhsIsExpanded) = rhs {
|
||||
if lhsTheme !== rhsTheme {
|
||||
return false
|
||||
}
|
||||
@ -208,6 +239,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
if lhsEnabled != rhsEnabled {
|
||||
return false
|
||||
}
|
||||
if lhsSubItems != rhsSubItems {
|
||||
return false
|
||||
}
|
||||
if lhsIsExpanded != rhsIsExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -256,11 +293,11 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case let .rightItem(_, lhsIndex, _, _, _, _, _):
|
||||
case let .rightItem(_, lhsIndex, _, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .info, .adminRights, .rightsTitle:
|
||||
return false
|
||||
case let .rightItem(_, rhsIndex, _, _, _, _, _):
|
||||
case let .rightItem(_, rhsIndex, _, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
default:
|
||||
return true
|
||||
@ -341,12 +378,48 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .rightsTitle(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .rightItem(_, _, text, right, flags, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { _ in
|
||||
arguments.toggleRight(right, flags)
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.toggleRightWhileDisabled(right, flags)
|
||||
})
|
||||
case let .rightItem(_, _, text, right, flags, value, enabled, subPermissions, isExpanded):
|
||||
if !subPermissions.isEmpty {
|
||||
return ItemListExpandableSwitchItem(presentationData: presentationData, title: text, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in
|
||||
return ItemListExpandableSwitchItem.SubItem(
|
||||
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):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .transfer(_, text):
|
||||
@ -362,18 +435,20 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
}
|
||||
|
||||
private struct ChannelAdminControllerState: Equatable {
|
||||
let adminRights: Bool
|
||||
let updatedFlags: TelegramChatAdminRightsFlags?
|
||||
let updatedRank: String?
|
||||
let updating: Bool
|
||||
let focusedOnRank: Bool
|
||||
var adminRights: Bool
|
||||
var updatedFlags: TelegramChatAdminRightsFlags?
|
||||
var updatedRank: String?
|
||||
var updating: 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.updatedFlags = updatedFlags
|
||||
self.updatedRank = updatedRank
|
||||
self.updating = updating
|
||||
self.focusedOnRank = focusedOnRank
|
||||
self.expandedPermissions = expandedPermissions
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelAdminControllerState, rhs: ChannelAdminControllerState) -> Bool {
|
||||
@ -392,27 +467,30 @@ private struct ChannelAdminControllerState: Equatable {
|
||||
if lhs.focusedOnRank != rhs.focusedOnRank {
|
||||
return false
|
||||
}
|
||||
if lhs.expandedPermissions != rhs.expandedPermissions {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
return strings.Channel_EditAdmin_PermissionPostMessages
|
||||
} 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) {
|
||||
return isGroup ? strings.Channel_EditAdmin_PermissionDeleteMessages : strings.Channel_EditAdmin_PermissionDeleteMessagesOfOthers
|
||||
} else if right.contains(.canBanUsers) {
|
||||
@ -451,6 +534,15 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
|
||||
} else {
|
||||
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 {
|
||||
return ""
|
||||
}
|
||||
@ -553,45 +645,45 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
|
||||
let isGroup: Bool
|
||||
var maskRightsFlags: TelegramChatAdminRightsFlags
|
||||
let rightsOrder: [TelegramChatAdminRightsFlags]
|
||||
|
||||
let rightsOrder: [RightsItem]
|
||||
|
||||
maskRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: .channel(channel))
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
isGroup = false
|
||||
rightsOrder = [
|
||||
.canChangeInfo,
|
||||
.canPostMessages,
|
||||
.canEditMessages,
|
||||
.canDeleteMessages,
|
||||
.canInviteUsers,
|
||||
.canManageCalls,
|
||||
.canAddAdmins
|
||||
.direct(.canChangeInfo),
|
||||
.sub(.messages, messageRelatedFlags),
|
||||
.sub(.stories, storiesRelatedFlags),
|
||||
.direct(.canInviteUsers),
|
||||
.direct(.canManageCalls),
|
||||
.direct(.canAddAdmins)
|
||||
]
|
||||
case .group:
|
||||
isGroup = true
|
||||
if channel.flags.contains(.isForum) {
|
||||
rightsOrder = [
|
||||
.canChangeInfo,
|
||||
.canDeleteMessages,
|
||||
.canBanUsers,
|
||||
.canInviteUsers,
|
||||
.canPinMessages,
|
||||
.canManageTopics,
|
||||
.canManageCalls,
|
||||
.canBeAnonymous,
|
||||
.canAddAdmins
|
||||
.direct(.canChangeInfo),
|
||||
.direct(.canDeleteMessages),
|
||||
.direct(.canBanUsers),
|
||||
.direct(.canInviteUsers),
|
||||
.direct(.canPinMessages),
|
||||
.direct(.canManageTopics),
|
||||
.direct(.canManageCalls),
|
||||
.direct(.canBeAnonymous),
|
||||
.direct(.canAddAdmins)
|
||||
]
|
||||
} else {
|
||||
rightsOrder = [
|
||||
.canChangeInfo,
|
||||
.canDeleteMessages,
|
||||
.canBanUsers,
|
||||
.canInviteUsers,
|
||||
.canPinMessages,
|
||||
.canManageCalls,
|
||||
.canBeAnonymous,
|
||||
.canAddAdmins
|
||||
.direct(.canChangeInfo),
|
||||
.direct(.canDeleteMessages),
|
||||
.direct(.canBanUsers),
|
||||
.direct(.canInviteUsers),
|
||||
.direct(.canPinMessages),
|
||||
.direct(.canManageCalls),
|
||||
.direct(.canBeAnonymous),
|
||||
.direct(.canAddAdmins)
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -621,11 +713,52 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for right in rightsOrder {
|
||||
if accountUserRightsFlags.contains(right) {
|
||||
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))
|
||||
index += 1
|
||||
rightsLoop: for right in rightsOrder {
|
||||
let enabled: Bool
|
||||
let isSelected: Bool
|
||||
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 {
|
||||
@ -660,11 +793,52 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for right in rightsOrder {
|
||||
if accountUserRightsFlags.contains(right) {
|
||||
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)))
|
||||
index += 1
|
||||
rightsLoop: for right in rightsOrder {
|
||||
let enabled: Bool
|
||||
let isSelected: Bool
|
||||
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) {
|
||||
@ -693,8 +867,43 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
}
|
||||
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo {
|
||||
var index = 0
|
||||
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))
|
||||
rightsLoop: for right in rightsOrder {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -792,7 +1001,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
var index = 0
|
||||
for right in rightsOrder {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -866,13 +1075,25 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
||||
updateState { current in
|
||||
return current.withUpdatedAdminRights(value)
|
||||
}
|
||||
}, toggleRight: { right, flags in
|
||||
}, toggleRight: { right, flags, value in
|
||||
updateState { current in
|
||||
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 {
|
||||
updated.insert(right)
|
||||
updated.insert(combinedRight)
|
||||
}
|
||||
return current.withUpdatedUpdatedFlags(updated)
|
||||
}
|
||||
@ -971,6 +1192,18 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
||||
dismissInputImpl?()
|
||||
}, animateError: {
|
||||
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
|
||||
|
@ -160,7 +160,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-531931925] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsMentions($0) }
|
||||
dict[-566281095] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsRecent($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[1103884886] = { return Api.Chat.parse_chat($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[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($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[821314523] = { return Api.Update.parse_updateRecentEmojiStatuses($0) }
|
||||
dict[1870160884] = { return Api.Update.parse_updateRecentReactions($0) }
|
||||
|
@ -1165,7 +1165,7 @@ public extension Api {
|
||||
case updateReadFeaturedStickers
|
||||
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 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 updateRecentEmojiStatuses
|
||||
case updateRecentReactions
|
||||
@ -2007,10 +2007,11 @@ public extension Api {
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateReadMessagesContents(let messages, let pts, let ptsCount):
|
||||
case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(1757493555)
|
||||
buffer.appendInt32(-131960447)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(messages.count))
|
||||
for item in messages {
|
||||
@ -2018,6 +2019,7 @@ public extension Api {
|
||||
}
|
||||
serializeInt32(pts, buffer: buffer, boxed: false)
|
||||
serializeInt32(ptsCount, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .updateReadStories(let peer, let maxId):
|
||||
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)])
|
||||
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)])
|
||||
case .updateReadMessagesContents(let messages, let pts, let ptsCount):
|
||||
return ("updateReadMessagesContents", [("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
|
||||
case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date):
|
||||
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):
|
||||
return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)])
|
||||
case .updateRecentEmojiStatuses:
|
||||
@ -4116,19 +4118,25 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? {
|
||||
var _1: [Int32]?
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Int32]?
|
||||
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?
|
||||
_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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.Update.updateReadMessagesContents(messages: _1!, pts: _2!, ptsCount: _3!)
|
||||
let _c4 = _4 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
@ -522,7 +522,7 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
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 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)
|
||||
@ -530,9 +530,9 @@ public extension Api {
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
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 {
|
||||
buffer.appendInt32(-2094689180)
|
||||
buffer.appendInt32(-1795845413)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(flags2, buffer: buffer, boxed: false)
|
||||
@ -556,6 +556,7 @@ public extension Api {
|
||||
for item in usernames! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags2) & Int(1 << 4) != 0 {serializeInt32(storiesMaxId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate):
|
||||
if boxed {
|
||||
@ -600,8 +601,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
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):
|
||||
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)])
|
||||
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), ("storiesMaxId", storiesMaxId as Any)])
|
||||
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)])
|
||||
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() {
|
||||
_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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -668,8 +671,9 @@ public extension Api {
|
||||
let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil
|
||||
let _c13 = (Int(_1!) & Int(1 << 17) == 0) || _13 != 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 {
|
||||
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)
|
||||
let _c15 = (Int(_2!) & Int(1 << 4) == 0) || _15 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
@ -124,6 +124,7 @@ enum AccountStateMutationOperation {
|
||||
case UpdateReadStories(peerId: PeerId, maxId: Int32)
|
||||
case UpdateStoryStealthMode(data: Api.StoriesStealthMode)
|
||||
case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction)
|
||||
case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String)
|
||||
}
|
||||
|
||||
struct HoleFromPreviousState {
|
||||
@ -467,7 +468,7 @@ struct AccountMutableState {
|
||||
|
||||
for chat in chats {
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||
if let participantsCount = participantsCount {
|
||||
self.addOperation(.UpdateCachedPeerData(chat.peerId, { current in
|
||||
var previous: CachedChannelData
|
||||
@ -653,9 +654,13 @@ struct AccountMutableState {
|
||||
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) {
|
||||
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
|
||||
case let .AddMessages(messages, location):
|
||||
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)
|
||||
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)
|
||||
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 participationStatus: TelegramChannelParticipationStatus
|
||||
@ -170,7 +170,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
||||
switch rhs {
|
||||
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
|
||||
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
|
||||
if accessHash != nil && !isMin {
|
||||
return parseTelegramGroupOrChannel(chat: rhs)
|
||||
|
@ -18,6 +18,9 @@ public enum TelegramChannelPermission {
|
||||
case changeInfo
|
||||
case canBeAnonymous
|
||||
case manageCalls
|
||||
case postStories
|
||||
case editStories
|
||||
case deleteStories
|
||||
}
|
||||
|
||||
public extension TelegramChannel {
|
||||
@ -235,6 +238,24 @@ public extension TelegramChannel {
|
||||
return true
|
||||
}
|
||||
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):
|
||||
let isPinned = (flags & (1 << 0)) != 0
|
||||
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
|
||||
case let .updateReadMessagesContents(messages, _, _):
|
||||
case let .updateReadMessagesContents(_, messages, _, _, _):
|
||||
updatedState.addReadMessagesContents((nil, messages))
|
||||
case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages):
|
||||
let _ = topMsgId
|
||||
@ -1681,6 +1681,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
updatedState.updateStoryStealthMode(stealthMode)
|
||||
case let .updateSentStoryReaction(peerId, storyId, 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:
|
||||
break
|
||||
}
|
||||
@ -3169,7 +3172,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||
for operation in operations {
|
||||
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 {
|
||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||
}
|
||||
@ -4586,6 +4589,7 @@ func replayFinalState(
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: updatedReaction
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
@ -4616,6 +4620,7 @@ func replayFinalState(
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: MessageReaction.Reaction(apiReaction: reaction)
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
@ -4623,6 +4628,20 @@ func replayFinalState(
|
||||
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 {
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var users: [PeerId: Api.User] = [:]
|
||||
var chats: [PeerId: Api.Chat] = [:]
|
||||
|
||||
var allIds: Set<PeerId> {
|
||||
var result = Set<PeerId>()
|
||||
@ -31,6 +32,9 @@ struct AccumulatedPeers {
|
||||
for user in users {
|
||||
self.users[user.peerId] = user
|
||||
}
|
||||
for chat in chats {
|
||||
self.chats[chat.peerId] = chat
|
||||
}
|
||||
}
|
||||
|
||||
init(chats: [Api.Chat], users: [Api.User]) {
|
||||
@ -42,6 +46,9 @@ struct AccumulatedPeers {
|
||||
for user in users {
|
||||
self.users[user.peerId] = user
|
||||
}
|
||||
for chat in chats {
|
||||
self.chats[chat.peerId] = chat
|
||||
}
|
||||
}
|
||||
|
||||
init(users: [Api.User]) {
|
||||
@ -65,6 +72,9 @@ struct AccumulatedPeers {
|
||||
for (id, user) in other.users {
|
||||
result.users[id] = user
|
||||
}
|
||||
for (id, chat) in other.chats {
|
||||
result.chats[id] = chat
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ func apiUpdatePtsRange(_ update: Api.Update) -> (Int32, Int32)? {
|
||||
return (pts, ptsCount)
|
||||
case let .updateEditMessage(_, pts, ptsCount):
|
||||
return (pts, ptsCount)
|
||||
case let .updateReadMessagesContents(_, pts, ptsCount):
|
||||
case let .updateReadMessagesContents(_, _, pts, ptsCount, _):
|
||||
return (pts, ptsCount)
|
||||
case let .updateWebPage(_, pts, ptsCount):
|
||||
return (pts, ptsCount)
|
||||
|
@ -181,7 +181,7 @@ extension Api.Chat {
|
||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id))
|
||||
case let .chatForbidden(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))
|
||||
case let .channelForbidden(_, id, _, _, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id))
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
public final class NewSessionReview: Codable, Equatable {
|
||||
struct Id {
|
||||
@ -19,11 +20,13 @@ public final class NewSessionReview: Codable, Equatable {
|
||||
public let id: Int64
|
||||
public let device: 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.device = device
|
||||
self.location = location
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
|
||||
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.device = try container.decode(String.self, forKey: "device")
|
||||
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 {
|
||||
@ -40,6 +44,7 @@ public final class NewSessionReview: Codable, Equatable {
|
||||
try container.encode(self.id, forKey: "id")
|
||||
try container.encode(self.device, forKey: "device")
|
||||
try container.encode(self.location, forKey: "location")
|
||||
try container.encode(self.timestamp, forKey: "timestamp")
|
||||
}
|
||||
|
||||
public static func ==(lhs: NewSessionReview, rhs: NewSessionReview) -> Bool {
|
||||
@ -52,10 +57,42 @@ public final class NewSessionReview: Codable, Equatable {
|
||||
if lhs.location != rhs.location {
|
||||
return false
|
||||
}
|
||||
if lhs.timestamp != rhs.timestamp {
|
||||
return false
|
||||
}
|
||||
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> {
|
||||
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.NewSessionReviews)
|
||||
return postbox.combinedView(keys: [viewKey])
|
||||
@ -102,3 +139,11 @@ public func removeNewSessionReviews(postbox: Postbox, ids: [Int64]) -> Signal<Ne
|
||||
}
|
||||
|> 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 canManageCalls = TelegramChatAdminRightsFlags(rawValue: 1 << 11)
|
||||
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 {
|
||||
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 {
|
||||
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 = [
|
||||
@ -49,7 +53,10 @@ public struct TelegramChatAdminRightsFlags: OptionSet, Hashable {
|
||||
.canDeleteMessages,
|
||||
.canManageCalls,
|
||||
.canInviteUsers,
|
||||
.canAddAdmins
|
||||
.canAddAdmins,
|
||||
.canPostStories,
|
||||
.canEditStories,
|
||||
.canDeleteStories
|
||||
]
|
||||
|
||||
public static func peerSpecific(peer: EnginePeer) -> TelegramChatAdminRightsFlags {
|
||||
|
@ -2254,7 +2254,7 @@ func _internal_groupCallDisplayAsAvailablePeers(accountPeerId: PeerId, network:
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||
if let participantsCount = participantsCount {
|
||||
subscribers[groupOrChannel.id] = participantsCount
|
||||
}
|
||||
|
@ -375,6 +375,7 @@ public final class EngineStoryViewListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
@ -411,6 +412,7 @@ public final class EngineStoryViewListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
|
@ -138,7 +138,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parsedPeers.get(chat.peerId) {
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||
if let participantsCount = participantsCount {
|
||||
subscribers[groupOrChannel.id] = participantsCount
|
||||
}
|
||||
|
@ -180,6 +180,7 @@ public enum Stories {
|
||||
case isSelectedContacts
|
||||
case isForwardingDisabled
|
||||
case isEdited
|
||||
case isMy
|
||||
case myReaction
|
||||
}
|
||||
|
||||
@ -200,6 +201,7 @@ public enum Stories {
|
||||
public let isSelectedContacts: Bool
|
||||
public let isForwardingDisabled: Bool
|
||||
public let isEdited: Bool
|
||||
public let isMy: Bool
|
||||
public let myReaction: MessageReaction.Reaction?
|
||||
|
||||
public init(
|
||||
@ -220,6 +222,7 @@ public enum Stories {
|
||||
isSelectedContacts: Bool,
|
||||
isForwardingDisabled: Bool,
|
||||
isEdited: Bool,
|
||||
isMy: Bool,
|
||||
myReaction: MessageReaction.Reaction?
|
||||
) {
|
||||
self.id = id
|
||||
@ -239,6 +242,7 @@ public enum Stories {
|
||||
self.isSelectedContacts = isSelectedContacts
|
||||
self.isForwardingDisabled = isForwardingDisabled
|
||||
self.isEdited = isEdited
|
||||
self.isMy = isMy
|
||||
self.myReaction = myReaction
|
||||
}
|
||||
|
||||
@ -268,6 +272,7 @@ public enum Stories {
|
||||
self.isSelectedContacts = try container.decodeIfPresent(Bool.self, forKey: .isSelectedContacts) ?? false
|
||||
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? 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)
|
||||
}
|
||||
|
||||
@ -298,6 +303,7 @@ public enum Stories {
|
||||
try container.encode(self.isSelectedContacts, forKey: .isSelectedContacts)
|
||||
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
|
||||
try container.encode(self.isEdited, forKey: .isEdited)
|
||||
try container.encode(self.isMy, forKey: .isMy)
|
||||
try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
|
||||
}
|
||||
|
||||
@ -1025,6 +1031,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
@ -1204,6 +1211,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
@ -1232,6 +1240,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
@ -1406,6 +1415,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
@ -1433,6 +1443,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
updatedItems.append(updatedItem)
|
||||
@ -1575,6 +1586,13 @@ extension Stories.StoredItem {
|
||||
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(
|
||||
id: id,
|
||||
timestamp: date,
|
||||
@ -1593,6 +1611,7 @@ extension Stories.StoredItem {
|
||||
isSelectedContacts: isSelectedContacts,
|
||||
isForwardingDisabled: isForwardingDisabled,
|
||||
isEdited: isEdited,
|
||||
isMy: mergedIsMy,
|
||||
myReaction: mergedMyReaction
|
||||
)
|
||||
self = .item(item)
|
||||
@ -2046,6 +2065,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: reaction
|
||||
))
|
||||
updatedItemValue = updatedItem
|
||||
@ -2076,6 +2096,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: reaction
|
||||
))
|
||||
updatedItemValue = updatedItem
|
||||
|
@ -69,9 +69,10 @@ public final class EngineStoryItem: Equatable {
|
||||
public let isSelectedContacts: Bool
|
||||
public let isForwardingDisabled: Bool
|
||||
public let isEdited: Bool
|
||||
public let isMy: Bool
|
||||
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.timestamp = timestamp
|
||||
self.expirationTimestamp = expirationTimestamp
|
||||
@ -90,6 +91,7 @@ public final class EngineStoryItem: Equatable {
|
||||
self.isSelectedContacts = isSelectedContacts
|
||||
self.isForwardingDisabled = isForwardingDisabled
|
||||
self.isEdited = isEdited
|
||||
self.isMy = isMy
|
||||
self.myReaction = myReaction
|
||||
}
|
||||
|
||||
@ -148,6 +150,9 @@ public final class EngineStoryItem: Equatable {
|
||||
if lhs.isEdited != rhs.isEdited {
|
||||
return false
|
||||
}
|
||||
if lhs.isMy != rhs.isMy {
|
||||
return false
|
||||
}
|
||||
if lhs.myReaction != rhs.myReaction {
|
||||
return false
|
||||
}
|
||||
@ -189,6 +194,7 @@ extension EngineStoryItem {
|
||||
isSelectedContacts: self.isSelectedContacts,
|
||||
isForwardingDisabled: self.isForwardingDisabled,
|
||||
isEdited: self.isEdited,
|
||||
isMy: self.isMy,
|
||||
myReaction: self.myReaction
|
||||
)
|
||||
}
|
||||
@ -563,6 +569,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
items.append(mappedItem)
|
||||
@ -693,6 +700,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
storyItems.append(mappedItem)
|
||||
@ -847,6 +855,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
finalUpdatedState = updatedState
|
||||
@ -892,6 +901,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
finalUpdatedState = updatedState
|
||||
@ -939,6 +949,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
))
|
||||
updatedState.items.sort(by: { lhs, rhs in
|
||||
@ -982,6 +993,7 @@ public final class PeerStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
))
|
||||
updatedState.items.sort(by: { lhs, rhs in
|
||||
@ -1149,6 +1161,7 @@ public final class PeerExpiringStoryListContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
items.append(.item(mappedItem))
|
||||
|
@ -1132,6 +1132,7 @@ public extension TelegramEngine {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
|
@ -280,7 +280,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
|
||||
for chat in chats {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||
if let participantsCount = participantsCount {
|
||||
memberCounts[chat.peerId] = Int(participantsCount)
|
||||
}
|
||||
@ -307,7 +307,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
|
||||
for chat in chats {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||
if let participantsCount = participantsCount {
|
||||
memberCounts[chat.peerId] = Int(participantsCount)
|
||||
}
|
||||
@ -621,7 +621,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
||||
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
|
||||
|
||||
for chat in chats {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat {
|
||||
if let participantsCount = 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] = [:]
|
||||
for chat in chats {
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _):
|
||||
if let participantsCountValue = participantsCountValue {
|
||||
participantsCounts[chat.peerId] = participantsCountValue
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _):
|
||||
if let participantsCount = participantsCount {
|
||||
subscribers[groupOrChannel.id] = participantsCount
|
||||
}
|
||||
|
@ -820,7 +820,7 @@ public extension TelegramEngine {
|
||||
|> beforeNext { _ in
|
||||
let delayTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
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> {
|
||||
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) {
|
||||
var participantCount: Int32? = nil
|
||||
switch chat {
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _):
|
||||
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCountValue, _, _):
|
||||
participantCount = participantsCountValue
|
||||
default:
|
||||
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 {
|
||||
parsedPeers.append(peer)
|
||||
}
|
||||
|
@ -2809,7 +2809,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
if let 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
|
||||
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 {
|
||||
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
||||
let source: ContextContentSource = .reference(StorageUsageContextReferenceContentSource(sourceView: sourceLabelView))
|
||||
|
@ -187,6 +187,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
isSelectedContacts: item.isSelectedContacts,
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited,
|
||||
isMy: item.isMy,
|
||||
myReaction: item.myReaction
|
||||
)
|
||||
}
|
||||
@ -220,6 +221,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
isSelectedContacts: item.privacy.base == .nobody,
|
||||
isForwardingDisabled: false,
|
||||
isEdited: false,
|
||||
isMy: true,
|
||||
myReaction: nil
|
||||
))
|
||||
totalCount += 1
|
||||
@ -1096,6 +1098,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
isSelectedContacts: itemValue.isSelectedContacts,
|
||||
isForwardingDisabled: itemValue.isForwardingDisabled,
|
||||
isEdited: itemValue.isEdited,
|
||||
isMy: itemValue.isMy,
|
||||
myReaction: itemValue.myReaction
|
||||
)
|
||||
|
||||
|
@ -5679,59 +5679,68 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
guard let component = self.component, let controller = component.controller() else {
|
||||
return
|
||||
}
|
||||
guard case let .channel(channel) = component.slice.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openStoryEditing()
|
||||
})))
|
||||
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openStoryEditing()
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.separator)
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
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)
|
||||
if channel.hasPermission(.editStories) {
|
||||
//TODO:localize
|
||||
if component.slice.item.storyItem.isPinned {
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: "Story removed from the channel's profile", timeout: 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)
|
||||
}
|
||||
})))
|
||||
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
|
||||
if component.slice.item.storyItem.isPinned {
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: "Story removed from the channel's profile", timeout: 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
|
||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||
@ -5745,23 +5754,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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) {
|
||||
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)
|
||||
@ -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)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] link in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] link in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -5803,48 +5795,50 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
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.sendMessageContext.actionSheet = actionSheet
|
||||
self.updateIsProgressPaused()
|
||||
|
||||
component.presentController(actionSheet, nil)
|
||||
})))
|
||||
|
||||
component.presentController(actionSheet, nil)
|
||||
})))
|
||||
}
|
||||
|
||||
let (tip, tipSignal) = self.getLinkedStickerPacks()
|
||||
|
||||
|
@ -5321,10 +5321,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let cachedData = strongSelf.data?.cachedData as? CachedChannelData {
|
||||
if channel.hasPermission(.sendSomething) {
|
||||
if channel.hasPermission(.editStories) {
|
||||
//TODO:localize
|
||||
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
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user