mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-24 12:10:49 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
fccae60f77
@ -5193,3 +5193,25 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"WallpaperPreview.PatternPaternApply" = "Apply";
|
||||
|
||||
"ChatContextMenu.TextSelectionTip" = "Hold a word, then move cursor to select more| text to copy.";
|
||||
|
||||
"OldChannels.Title" = "Limit Reached";
|
||||
"OldChannels.NoticeTitle" = "Too Many Groups and Channels";
|
||||
"OldChannels.NoticeText" = "Sorry, you are member of too many groups and channels.\nPlease leave some before joining new one.";
|
||||
"OldChannels.ChannelsHeader" = "MOST INACTIVE";
|
||||
"OldChannels.Leave" = "Leave";
|
||||
|
||||
"OldChannels.ChannelFormat" = "channel, ";
|
||||
"OldChannels.GroupEmptyFormat" = "group, ";
|
||||
"OldChannels.GroupFormat_1" = "%@ member";
|
||||
"OldChannels.GroupFormat_any" = "%@ members";
|
||||
|
||||
"OldChannels.InactiveWeek_1" = "inactive %@ week";
|
||||
"OldChannels.InactiveWeek_any" = "inactive %@ weeks";
|
||||
|
||||
"OldChannels.InactiveMonth_1" = "inactive %@ month";
|
||||
"OldChannels.InactiveMonth_any" = "inactive %@ months";
|
||||
|
||||
"OldChannels.InactiveYear_1" = "inactive %@ year";
|
||||
"OldChannels.InactiveYear_any" = "inactive %@ years";
|
||||
|
||||
"PrivacySettings.WebSessions" = "Active Websites";
|
||||
|
@ -283,7 +283,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
self.imageNode.isHidden = true
|
||||
}
|
||||
|
||||
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false) {
|
||||
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0)) {
|
||||
var synchronousLoad = synchronousLoad
|
||||
var representation: TelegramMediaImageRepresentation?
|
||||
var icon = AvatarNodeIcon.none
|
||||
@ -318,7 +318,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
|
||||
let parameters: AvatarNodeParameters
|
||||
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
|
||||
if let peer = peer, let signal = peerAvatarImage(account: context.account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
|
||||
self.contents = nil
|
||||
self.displaySuspended = true
|
||||
self.imageReady.set(self.imageNode.ready)
|
||||
|
@ -87,7 +87,7 @@ public final class BotCheckoutController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
|
@ -705,10 +705,10 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
||||
var updatedInsets = layout.intrinsicInsets
|
||||
updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0
|
||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter))
|
||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||
|
@ -81,7 +81,7 @@ public final class BotReceiptController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
|
@ -301,10 +301,10 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
||||
self.dataRequestDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
||||
var updatedInsets = layout.intrinsicInsets
|
||||
updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0
|
||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets)
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter))
|
||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||
|
@ -547,7 +547,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads)
|
||||
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
|
||||
}
|
||||
|
||||
self.contextContainer.isGestureEnabled = enablePreview
|
||||
|
@ -105,8 +105,10 @@ public enum ContactsPeerItemPeer: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public class ContactsPeerItem: ListViewItem, ListViewItemWithHeader {
|
||||
public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
let presentationData: ItemListPresentationData
|
||||
let style: ItemListStyle
|
||||
public let sectionId: ItemListSectionId
|
||||
let sortOrder: PresentationPersonNameOrder
|
||||
let displayOrder: PresentationPersonNameOrder
|
||||
let context: AccountContext
|
||||
@ -131,8 +133,10 @@ public class ContactsPeerItem: ListViewItem, ListViewItemWithHeader {
|
||||
|
||||
public let header: ListViewItemHeader?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.style = style
|
||||
self.sectionId = sectionId
|
||||
self.sortOrder = sortOrder
|
||||
self.displayOrder = displayOrder
|
||||
self.context = context
|
||||
@ -208,7 +212,7 @@ public class ContactsPeerItem: ListViewItem, ListViewItemWithHeader {
|
||||
let node = ContactsPeerItemNode()
|
||||
let makeLayout = node.asyncLayout()
|
||||
let (first, last, firstWithHeader) = ContactsPeerItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, nodeApply) = makeLayout(self, params, first, last, firstWithHeader)
|
||||
let (nodeLayout, nodeApply) = makeLayout(self, params, first, last, firstWithHeader, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
node.insets = nodeLayout.insets
|
||||
|
||||
@ -229,7 +233,7 @@ public class ContactsPeerItem: ListViewItem, ListViewItemWithHeader {
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (first, last, firstWithHeader) = ContactsPeerItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader)
|
||||
let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, { _ in
|
||||
apply().1(animation.isAnimated, false)
|
||||
@ -279,6 +283,7 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||
|
||||
public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
@ -296,7 +301,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private var isHighlighted: Bool = false
|
||||
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
private var layoutParams: (ContactsPeerItem, ListViewItemLayoutParams, Bool, Bool, Bool)?
|
||||
private var layoutParams: (ContactsPeerItem, ListViewItemLayoutParams, Bool, Bool, Bool, ItemListNeighbors)?
|
||||
public var chatPeer: Peer? {
|
||||
if let peer = self.layoutParams?.0.peer {
|
||||
switch peer {
|
||||
@ -318,6 +323,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topSeparatorNode = ASDisplayNode()
|
||||
self.topSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
@ -337,6 +345,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
self.isAccessibilityElement = true
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.topSeparatorNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
@ -345,7 +354,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4)
|
||||
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4, layoutParams.5)
|
||||
let _ = apply()
|
||||
}
|
||||
})
|
||||
@ -360,11 +369,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
if let (item, _, _, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _, _, _) = self.layoutParams {
|
||||
let (first, last, firstWithHeader) = ContactsPeerItem.mergeType(item: item, previousItem: previousItem, nextItem: nextItem)
|
||||
self.layoutParams = (item, params, first, last, firstWithHeader)
|
||||
self.layoutParams = (item, params, first, last, firstWithHeader, itemListNeighbors(item: item, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
let makeLayout = self.asyncLayout()
|
||||
let (nodeLayout, nodeApply) = makeLayout(item, params, first, last, firstWithHeader)
|
||||
let (nodeLayout, nodeApply) = makeLayout(item, params, first, last, firstWithHeader, itemListNeighbors(item: item, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
self.contentSize = nodeLayout.contentSize
|
||||
self.insets = nodeLayout.insets
|
||||
let _ = nodeApply()
|
||||
@ -419,7 +428,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) {
|
||||
public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let currentSelectionNode = self.selectionNode
|
||||
@ -428,7 +437,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
|
||||
return { [weak self] item, params, first, last, firstWithHeader in
|
||||
return { [weak self] item, params, first, last, firstWithHeader, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
@ -660,7 +669,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
if let strongSelf = self {
|
||||
return (.complete(), { [weak strongSelf] animated, synchronousLoads in
|
||||
if let strongSelf = strongSelf {
|
||||
strongSelf.layoutParams = (item, params, first, last, firstWithHeader)
|
||||
strongSelf.layoutParams = (item, params, first, last, firstWithHeader, neighbors)
|
||||
|
||||
strongSelf.accessibilityLabel = titleAttributedString?.string
|
||||
strongSelf.accessibilityValue = statusAttributedString?.string
|
||||
@ -703,11 +712,31 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
let revealOffset = strongSelf.revealOffset
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
switch item.style {
|
||||
case .plain:
|
||||
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
case .blocks:
|
||||
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
}
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
}
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
strongSelf.topSeparatorNode.isHidden = true
|
||||
case .blocks:
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topSeparatorNode.isHidden = true
|
||||
default:
|
||||
strongSelf.topSeparatorNode.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)))
|
||||
|
||||
let _ = titleApply()
|
||||
@ -824,6 +853,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
||||
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(nodeLayout.insets.top, separatorHeight)), size: CGSize(width: nodeLayout.contentSize.width, height: separatorHeight))
|
||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight))
|
||||
strongSelf.separatorNode.isHidden = last
|
||||
|
||||
@ -955,7 +985,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
if let (item, _, _, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _, _, _) = self.layoutParams {
|
||||
return item.header
|
||||
} else {
|
||||
return nil
|
||||
|
@ -115,6 +115,8 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
public var additionalInsets: UIEdgeInsets = UIEdgeInsets()
|
||||
|
||||
private var didPlayPresentationAnimation = false
|
||||
public private(set) var didAppearOnce = false
|
||||
public var didAppear: ((Bool) -> Void)?
|
||||
@ -459,7 +461,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
(self.displayNode as! ItemListControllerNode).containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, transition: transition)
|
||||
(self.displayNode as! ItemListControllerNode).containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, transition: transition, additionalInsets: self.additionalInsets)
|
||||
}
|
||||
|
||||
@objc func leftNavigationButtonPressed() {
|
||||
|
@ -369,9 +369,10 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
open func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
open func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) {
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
insets.bottom = max(insets.bottom, additionalInsets.bottom)
|
||||
|
||||
var addedInsets: UIEdgeInsets?
|
||||
if layout.size.width > 480.0 {
|
||||
@ -551,7 +552,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let validLayout = self.validLayout {
|
||||
updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
|
||||
}
|
||||
self.insertSubnode(updatedNode, belowSubnode: self.navigationBar)
|
||||
self.insertSubnode(updatedNode, aboveSubnode: self.listNode)
|
||||
updatedNode.activate()
|
||||
}
|
||||
} else {
|
||||
@ -684,6 +685,11 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let searchNode = self.searchNode {
|
||||
if !self.navigationBar.isHidden && self.navigationBar.supernode != nil {
|
||||
if let result = self.navigationBar.hitTest(self.view.convert(point, to: self.navigationBar.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
if let result = searchNode.hitTest(point, with: event) {
|
||||
return result
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
|
||||
titleString.addAttributes([NSAttributedString.Key.font: titleFont], range: NSMakeRange(0, titleString.length))
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 4.0)), insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 22.0)), insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
|
@ -62,6 +62,8 @@ static_library(
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||
"//submodules/TelegramIntents:TelegramIntents",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
456
submodules/PeerInfoUI/Sources/OldChannelsController.swift
Normal file
456
submodules/PeerInfoUI/Sources/OldChannelsController.swift
Normal file
@ -0,0 +1,456 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import ContactsPeerItem
|
||||
import SearchUI
|
||||
import SolidRoundedButtonNode
|
||||
|
||||
func localizedOldChannelDate(peer: InactiveChannel, strings: PresentationStrings) -> String {
|
||||
let timestamp = peer.lastActivityDate
|
||||
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
|
||||
var t: time_t = time_t(TimeInterval(timestamp))
|
||||
var timeinfo: tm = tm()
|
||||
localtime_r(&t, &timeinfo)
|
||||
|
||||
var now: time_t = time_t(nowTimestamp)
|
||||
var timeinfoNow: tm = tm()
|
||||
localtime_r(&now, &timeinfoNow)
|
||||
|
||||
var string: String
|
||||
|
||||
if timeinfoNow.tm_year == timeinfo.tm_year && timeinfoNow.tm_mon == timeinfo.tm_mon {
|
||||
//weeks
|
||||
let dif = Int(roundf(Float(timeinfoNow.tm_mday - timeinfo.tm_mday) / 7))
|
||||
string = strings.OldChannels_InactiveWeek(Int32(dif))
|
||||
} else if timeinfoNow.tm_year == timeinfo.tm_year {
|
||||
//month
|
||||
let dif = Int(timeinfoNow.tm_mon - timeinfo.tm_mon)
|
||||
string = strings.OldChannels_InactiveMonth(Int32(dif))
|
||||
} else {
|
||||
//year
|
||||
var dif = Int(timeinfoNow.tm_year - timeinfo.tm_year)
|
||||
|
||||
if Int(timeinfoNow.tm_mon - timeinfo.tm_mon) > 6 {
|
||||
dif += 1
|
||||
}
|
||||
string = strings.OldChannels_InactiveYear(Int32(dif))
|
||||
}
|
||||
|
||||
if let channel = peer.peer as? TelegramChannel, case .group = channel.info {
|
||||
if let participantsCount = peer.participantsCount, participantsCount != 0 {
|
||||
string = strings.OldChannels_GroupFormat(participantsCount) + string
|
||||
} else {
|
||||
string = strings.OldChannels_GroupEmptyFormat + string
|
||||
}
|
||||
} else {
|
||||
string = strings.OldChannels_ChannelFormat + string
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
private final class OldChannelsItemArguments {
|
||||
let context: AccountContext
|
||||
let togglePeer: (PeerId) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
togglePeer: @escaping (PeerId) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.togglePeer = togglePeer
|
||||
}
|
||||
}
|
||||
|
||||
private enum OldChannelsSection: Int32 {
|
||||
case info
|
||||
case peers
|
||||
}
|
||||
|
||||
private enum OldChannelsEntryId: Hashable {
|
||||
case info
|
||||
case peersHeader
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
private enum OldChannelsEntry: ItemListNodeEntry {
|
||||
case info(String, String)
|
||||
case peersHeader(String)
|
||||
case peer(Int, InactiveChannel, Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .info:
|
||||
return OldChannelsSection.info.rawValue
|
||||
case .peersHeader, .peer:
|
||||
return OldChannelsSection.peers.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: OldChannelsEntryId {
|
||||
switch self {
|
||||
case .info:
|
||||
return .info
|
||||
case .peersHeader:
|
||||
return .peersHeader
|
||||
case let .peer(_, peer, _):
|
||||
return .peer(peer.peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .info(title, text):
|
||||
if case .info(title, text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peersHeader(title):
|
||||
if case .peersHeader(title) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsIndex, lhsPeer, lhsSelected):
|
||||
if case let .peer(rhsIndex, rhsPeer, rhsSelected) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsSelected != rhsSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool {
|
||||
switch lhs {
|
||||
case .info:
|
||||
if case .info = rhs {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
case .peersHeader:
|
||||
switch rhs {
|
||||
case .info, .peersHeader:
|
||||
return false
|
||||
case .peer:
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _):
|
||||
switch rhs {
|
||||
case .info, .peersHeader:
|
||||
return false
|
||||
case let .peer(rhsIndex, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! OldChannelsItemArguments
|
||||
switch self {
|
||||
case let .info(title, text):
|
||||
return ItemListInfoItem(presentationData: presentationData, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: nil)
|
||||
case let .peersHeader(title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .peer(_, peer, selected):
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .custom(localizedOldChannelDate(peer: peer, strings: presentationData.strings)), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
arguments.togglePeer(peer.peer.id)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OldChannelsState: Equatable {
|
||||
var selectedPeers: Set<PeerId> = Set()
|
||||
var isSearching: Bool = false
|
||||
}
|
||||
|
||||
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, peers: [InactiveChannel]?) -> [OldChannelsEntry] {
|
||||
var entries: [OldChannelsEntry] = []
|
||||
|
||||
if let peers = peers, !peers.isEmpty {
|
||||
entries.append(.info(presentationData.strings.OldChannels_NoticeTitle, presentationData.strings.OldChannels_NoticeText))
|
||||
|
||||
entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader))
|
||||
for peer in peers {
|
||||
entries.append(.peer(entries.count, peer, state.selectedPeers.contains(peer.peer.id)))
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
private final class OldChannelsActionPanelNode: ASDisplayNode {
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
init(presentationData: ItemListPresentationData, leaveAction: @escaping () -> Void) {
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
|
||||
self.buttonNode = SolidRoundedButtonNode(title: presentationData.strings.OldChannels_Leave, icon: nil, theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.pressed = {
|
||||
leaveAction()
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: ItemListPresentationData) {
|
||||
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
|
||||
self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
}
|
||||
|
||||
func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let verticalInset: CGFloat = 16.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
|
||||
let insets = layout.insets(options: [.input])
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
self.buttonNode.updateLayout(width: layout.size.width - sideInset * 2.0, transition: transition)
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: CGSize(width: layout.size.width, height: buttonHeight)))
|
||||
|
||||
return buttonHeight + verticalInset * 2.0 + insets.bottom
|
||||
}
|
||||
}
|
||||
|
||||
private final class OldChannelsControllerImpl: ItemListController {
|
||||
private let panelNode: OldChannelsActionPanelNode
|
||||
|
||||
private var displayPanel: Bool = false
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
var leaveAction: (() -> Void)?
|
||||
|
||||
override init<ItemGenerationArguments>(presentationData: ItemListPresentationData, updatedPresentationData: Signal<ItemListPresentationData, NoError>, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?) {
|
||||
var leaveActionImpl: (() -> Void)?
|
||||
self.panelNode = OldChannelsActionPanelNode(presentationData: presentationData, leaveAction: {
|
||||
leaveActionImpl?()
|
||||
})
|
||||
|
||||
super.init(presentationData: presentationData, updatedPresentationData: updatedPresentationData, state: state, tabBarItem: tabBarItem)
|
||||
|
||||
self.presentationDataDisposable = (updatedPresentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.panelNode.updatePresentationData(presentationData)
|
||||
})
|
||||
|
||||
leaveActionImpl = { [weak self] in
|
||||
self?.leaveAction?()
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
override var navigationBarRequiresEntireLayoutUpdate: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
super.loadDisplayNode()
|
||||
|
||||
self.displayNode.addSubnode(self.panelNode)
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
let panelHeight = self.panelNode.updateLayout(layout, transition: transition)
|
||||
|
||||
var additionalInsets = UIEdgeInsets()
|
||||
additionalInsets.bottom = max(layout.intrinsicInsets.bottom, panelHeight)
|
||||
|
||||
self.additionalInsets = additionalInsets
|
||||
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.panelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.displayPanel ? (layout.size.height - panelHeight) : layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)), beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
func updateDisplayPanel(_ value: Bool) {
|
||||
if self.displayPanel != value {
|
||||
self.displayPanel = value
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func oldChannelsController(context: AccountContext) -> ViewController {
|
||||
let initialState = OldChannelsState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((OldChannelsState) -> OldChannelsState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var updateHasSelectedPeersImpl: ((Bool) -> Void)?
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var setDisplayNavigationBarImpl: ((Bool) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let arguments = OldChannelsItemArguments(
|
||||
context: context,
|
||||
togglePeer: { peerId in
|
||||
var hasSelectedPeers = false
|
||||
updateState { state in
|
||||
var state = state
|
||||
if state.selectedPeers.contains(peerId) {
|
||||
state.selectedPeers.remove(peerId)
|
||||
} else {
|
||||
state.selectedPeers.insert(peerId)
|
||||
}
|
||||
hasSelectedPeers = !state.selectedPeers.isEmpty
|
||||
return state
|
||||
}
|
||||
updateHasSelectedPeersImpl?(hasSelectedPeers)
|
||||
}
|
||||
)
|
||||
|
||||
let selectedPeerIds = statePromise.get()
|
||||
|> map { $0.selectedPeers }
|
||||
|> distinctUntilChanged
|
||||
|
||||
let peersSignal: Signal<[InactiveChannel]?, NoError> = .single(nil)
|
||||
|> then(
|
||||
inactiveChannelList(network: context.account.network)
|
||||
|> map { peers -> [InactiveChannel]? in
|
||||
return peers.sorted(by: { lhs, rhs in
|
||||
return lhs.lastActivityDate < rhs.lastActivityDate
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
let peersPromise = Promise<[InactiveChannel]?>()
|
||||
peersPromise.set(peersSignal)
|
||||
|
||||
var previousPeersWereEmpty = true
|
||||
|
||||
let signal = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
statePromise.get(),
|
||||
peersPromise.get()
|
||||
)
|
||||
|> map { presentationData, state, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.OldChannels_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
|
||||
var searchItem: OldChannelsSearchItem?
|
||||
searchItem = OldChannelsSearchItem(context: context, theme: presentationData.theme, placeholder: presentationData.strings.Common_Search, activated: state.isSearching, updateActivated: { value in
|
||||
if !value {
|
||||
setDisplayNavigationBarImpl?(true)
|
||||
}
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.isSearching = value
|
||||
return state
|
||||
}
|
||||
if value {
|
||||
setDisplayNavigationBarImpl?(false)
|
||||
}
|
||||
}, peers: peersPromise.get() |> map { $0 ?? [] }, selectedPeerIds: selectedPeerIds, togglePeer: { peerId in
|
||||
arguments.togglePeer(peerId)
|
||||
})
|
||||
|
||||
let peersAreEmpty = peers == nil
|
||||
let peersAreEmptyUpdated = previousPeersWereEmpty != peersAreEmpty
|
||||
previousPeersWereEmpty = peersAreEmpty
|
||||
|
||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
if peersAreEmpty {
|
||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||
}
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, peers: peers), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = OldChannelsControllerImpl(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
updateHasSelectedPeersImpl = { [weak controller] value in
|
||||
controller?.updateDisplayPanel(value)
|
||||
}
|
||||
|
||||
controller.leaveAction = {
|
||||
let state = stateValue.with { $0 }
|
||||
let _ = (peersPromise.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { peers in
|
||||
return context.account.postbox.transaction { transaction -> Void in
|
||||
if let peers = peers {
|
||||
for peer in peers {
|
||||
if transaction.getPeer(peer.peer.id) == nil {
|
||||
updatePeers(transaction: transaction, peers: [peer.peer], update: { _, updated in
|
||||
return updated
|
||||
})
|
||||
}
|
||||
removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peer.peer.id, reportChatSpam: false, deleteGloballyIfPossible: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
dismissImpl?()
|
||||
}
|
||||
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
setDisplayNavigationBarImpl = { [weak controller] display in
|
||||
controller?.setDisplayNavigationBar(display, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
418
submodules/PeerInfoUI/Sources/OldChannelsSearch.swift
Normal file
418
submodules/PeerInfoUI/Sources/OldChannelsSearch.swift
Normal file
@ -0,0 +1,418 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import MergeLists
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import SearchBarNode
|
||||
import SearchUI
|
||||
import ChatListSearchItemHeader
|
||||
import ContactsPeerItem
|
||||
|
||||
extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationContentNode {
|
||||
public func activate() {
|
||||
}
|
||||
|
||||
public func deactivate() {
|
||||
}
|
||||
|
||||
public func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
||||
}
|
||||
}
|
||||
|
||||
final class OldChannelsSearchItem: ItemListControllerSearch {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let placeholder: String
|
||||
let activated: Bool
|
||||
let updateActivated: (Bool) -> Void
|
||||
let peers: Signal<[InactiveChannel], NoError>
|
||||
let selectedPeerIds: Signal<Set<PeerId>, NoError>
|
||||
let togglePeer: (PeerId) -> Void
|
||||
|
||||
private var updateActivity: ((Bool) -> Void)?
|
||||
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
|
||||
private let activityDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal<Set<PeerId>, NoError>, togglePeer: @escaping (PeerId) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.placeholder = placeholder
|
||||
self.activated = activated
|
||||
self.updateActivated = updateActivated
|
||||
self.peers = peers
|
||||
self.selectedPeerIds = selectedPeerIds
|
||||
self.togglePeer = togglePeer
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.activityDisposable.dispose()
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerSearch) -> Bool {
|
||||
if let to = to as? OldChannelsSearchItem {
|
||||
if self.context !== to.context || self.theme !== to.theme || self.placeholder != to.placeholder || self.activated != to.activated {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode {
|
||||
let updateActivated: (Bool) -> Void = self.updateActivated
|
||||
if let current = current as? NavigationBarSearchContentNode {
|
||||
current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder)
|
||||
return current
|
||||
} else {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
return NavigationBarSearchContentNode(theme: presentationData.theme, placeholder: presentationData.strings.Settings_Search, activate: {
|
||||
updateActivated(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode {
|
||||
let updateActivated: (Bool) -> Void = self.updateActivated
|
||||
|
||||
if let current = current as? OldChannelsSearchItemNode, let titleContentNode = titleContentNode as? NavigationBarSearchContentNode {
|
||||
current.updatePresentationData(self.context.sharedContext.currentPresentationData.with { $0 })
|
||||
if current.isSearching != self.activated {
|
||||
if self.activated {
|
||||
current.activateSearch(placeholderNode: titleContentNode.placeholderNode)
|
||||
} else {
|
||||
current.deactivateSearch(placeholderNode: titleContentNode.placeholderNode)
|
||||
}
|
||||
}
|
||||
return current
|
||||
} else {
|
||||
return OldChannelsSearchItemNode(context: self.context, cancel: {
|
||||
updateActivated(false)
|
||||
}, peers: self.peers, selectedPeerIds: self.selectedPeerIds, togglePeer: self.togglePeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class OldChannelsSearchInteraction {
|
||||
let togglePeer: (PeerId) -> Void
|
||||
|
||||
init(togglePeer: @escaping (PeerId) -> Void) {
|
||||
self.togglePeer = togglePeer
|
||||
}
|
||||
}
|
||||
|
||||
private enum OldChannelsSearchEntry: Comparable, Identifiable {
|
||||
case peer(Int, InactiveChannel, Bool)
|
||||
|
||||
var stableId: PeerId {
|
||||
switch self {
|
||||
case let .peer(_, peer, _):
|
||||
return peer.peer.id
|
||||
}
|
||||
}
|
||||
|
||||
private func index() -> Int {
|
||||
switch self {
|
||||
case let .peer(index, _, _):
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: OldChannelsSearchEntry, rhs: OldChannelsSearchEntry) -> Bool {
|
||||
return lhs.index() < rhs.index()
|
||||
}
|
||||
|
||||
static func ==(lhs: OldChannelsSearchEntry, rhs: OldChannelsSearchEntry) -> Bool {
|
||||
if case let .peer(index, peer, isSelected) = lhs {
|
||||
if case .peer(index, peer, isSelected) = rhs {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .peer(_, peer, selected):
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .custom(localizedOldChannelDate(peer: peer, strings: presentationData.strings)), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
interaction.togglePeer(peer.peer.id)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OldChannelsSearchContainerTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let isSearching: Bool
|
||||
}
|
||||
|
||||
private func preparedOldChannelsSearchContainerTransition(presentationData: ItemListPresentationData, from fromEntries: [OldChannelsSearchEntry], to toEntries: [OldChannelsSearchEntry], context: AccountContext, interaction: OldChannelsSearchInteraction, isSearching: Bool, forceUpdate: Bool) -> OldChannelsSearchContainerTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
|
||||
return OldChannelsSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
|
||||
}
|
||||
|
||||
private final class OldChannelsSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private let listNode: ListView
|
||||
|
||||
private var enqueuedTransitions: [OldChannelsSearchContainerTransition] = []
|
||||
private var hasValidLayout = false
|
||||
|
||||
private let searchQuery = Promise<String?>()
|
||||
private let searchDisposable = MetaDisposable()
|
||||
|
||||
private var recentDisposable: Disposable?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
|
||||
init(context: AccountContext, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal<Set<PeerId>, NoError>, togglePeer: @escaping (PeerId) -> Void) {
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
self.listNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
let interaction = OldChannelsSearchInteraction(togglePeer: { peerId in
|
||||
togglePeer(peerId)
|
||||
})
|
||||
|
||||
let queryAndFoundItems: Signal<(String, [OldChannelsSearchEntry])?, NoError> = combineLatest(self.searchQuery.get(), peers, selectedPeerIds)
|
||||
|> mapToSignal { query, peers, selectedPeerIds -> Signal<(String, [OldChannelsSearchEntry])?, NoError> in
|
||||
if let query = query, !query.isEmpty {
|
||||
var results: [OldChannelsSearchEntry] = []
|
||||
let normalizedQuery = query.lowercased()
|
||||
for peer in peers {
|
||||
if peer.peer.indexName.matchesByTokens(normalizedQuery) {
|
||||
results.append(.peer(results.count, peer, selectedPeerIds.contains(peer.peer.id)))
|
||||
}
|
||||
}
|
||||
return .single((query, results))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
let previousEntriesHolder = Atomic<([OldChannelsSearchEntry], PresentationTheme, PresentationStrings)?>(value: nil)
|
||||
self.searchDisposable.set(combineLatest(queue: .mainQueue(), queryAndFoundItems, self.presentationDataPromise.get()).start(next: { [weak self] queryAndFoundItems, presentationData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var currentQuery: String?
|
||||
var entries: [OldChannelsSearchEntry] = []
|
||||
if let (query, items) = queryAndFoundItems {
|
||||
currentQuery = query
|
||||
for item in items {
|
||||
entries.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
if !entries.isEmpty || currentQuery == nil {
|
||||
let previousEntriesAndPresentationData = previousEntriesHolder.swap((entries, presentationData.theme, presentationData.strings))
|
||||
let transition = preparedOldChannelsSearchContainerTransition(presentationData: ItemListPresentationData(presentationData), from: previousEntriesAndPresentationData?.0 ?? [], to: entries, context: context, interaction: interaction, isSearching: queryAndFoundItems != nil, forceUpdate: previousEntriesAndPresentationData?.1 !== presentationData.theme || previousEntriesAndPresentationData?.2 !== presentationData.strings)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
}
|
||||
}))
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||
strongSelf.presentationDataPromise.set(.single(presentationData))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.beganInteractiveDragging = { [weak self] in
|
||||
self?.dismissInput?()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.searchDisposable.dispose()
|
||||
self.recentDisposable?.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.listNode.backgroundColor = theme.chatList.backgroundColor
|
||||
}
|
||||
|
||||
override func searchTextUpdated(text: String) {
|
||||
if text.isEmpty {
|
||||
self.searchQuery.set(.single(nil))
|
||||
} else {
|
||||
self.searchQuery.set(.single(text))
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: OldChannelsSearchContainerTransition) {
|
||||
self.enqueuedTransitions.append(transition)
|
||||
|
||||
if self.hasValidLayout {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransition() {
|
||||
if let transition = self.enqueuedTransitions.first {
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
|
||||
let isSearching = transition.isSearching
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
self?.listNode.isHidden = !isSearching
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
if !self.hasValidLayout {
|
||||
self.hasValidLayout = true
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func scrollToTop() {
|
||||
let listNodeToScroll: ListView = self.listNode
|
||||
listNodeToScroll.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.cancel?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class OldChannelsSearchItemNode: ItemListControllerSearchNode {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
var cancel: () -> Void
|
||||
private let peers: Signal<[InactiveChannel], NoError>
|
||||
private let selectedPeerIds: Signal<Set<PeerId>, NoError>
|
||||
private let togglePeer: (PeerId) -> Void
|
||||
|
||||
init(context: AccountContext, cancel: @escaping () -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal<Set<PeerId>, NoError>, togglePeer: @escaping (PeerId) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.cancel = cancel
|
||||
self.peers = peers
|
||||
self.selectedPeerIds = selectedPeerIds
|
||||
self.togglePeer = togglePeer
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
guard let (containerLayout, navigationBarHeight) = self.containerLayout, self.searchDisplayController == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: OldChannelsSearchContainerNode(context: self.context, peers: self.peers, selectedPeerIds: self.selectedPeerIds, togglePeer: self.togglePeer), cancel: { [weak self] in
|
||||
self?.cancel()
|
||||
})
|
||||
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
|
||||
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
|
||||
if isSearchBar {
|
||||
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
|
||||
} else {
|
||||
strongSelf.addSubnode(subnode)
|
||||
}
|
||||
}
|
||||
}, placeholder: placeholderNode)
|
||||
}
|
||||
|
||||
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.deactivate(placeholder: placeholderNode)
|
||||
self.searchDisplayController = nil
|
||||
}
|
||||
}
|
||||
|
||||
var isSearching: Bool {
|
||||
return self.searchDisplayController != nil
|
||||
}
|
||||
|
||||
override func scrollToTop() {
|
||||
self.searchDisplayController?.contentNode.scrollToTop()
|
||||
}
|
||||
|
||||
override func queryUpdated(_ query: String) {
|
||||
}
|
||||
|
||||
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let searchDisplayController = self.searchDisplayController, let result = searchDisplayController.contentNode.hitTest(self.view.convert(point, to: searchDisplayController.contentNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
@ -353,11 +353,13 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings:
|
||||
}
|
||||
}
|
||||
|
||||
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeSessionsCount: Int, twoStepAuthData: TwoStepVerificationAccessConfiguration?) -> [PrivacyAndSecurityEntry] {
|
||||
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeWebsitesCount: Int, twoStepAuthData: TwoStepVerificationAccessConfiguration?) -> [PrivacyAndSecurityEntry] {
|
||||
var entries: [PrivacyAndSecurityEntry] = []
|
||||
|
||||
entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)")))
|
||||
entries.append(.activeSessions(presentationData.theme, presentationData.strings.PrivacySettings_AuthSessions, activeSessionsCount == 0 ? "" : "\(activeSessionsCount)"))
|
||||
if activeWebsitesCount != 0 {
|
||||
entries.append(.activeSessions(presentationData.theme, presentationData.strings.PrivacySettings_WebSessions, activeWebsitesCount == 0 ? "" : "\(activeWebsitesCount)"))
|
||||
}
|
||||
|
||||
let passcodeValue: String
|
||||
switch accessChallengeData {
|
||||
@ -427,7 +429,7 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
||||
return entries
|
||||
}
|
||||
|
||||
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil) -> ViewController {
|
||||
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil) -> ViewController {
|
||||
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
|
||||
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
|
||||
@ -449,10 +451,12 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
let privacySettingsPromise = Promise<AccountPrivacySettings?>()
|
||||
privacySettingsPromise.set(.single(initialSettings) |> then(requestAccountPrivacySettings(account: context.account) |> map(Optional.init)))
|
||||
|
||||
let blockedPeersContext = BlockedPeersContext(account: context.account)
|
||||
let blockedPeersContext = blockedPeersContext ?? BlockedPeersContext(account: context.account)
|
||||
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account)
|
||||
let webSessionsContext = webSessionsContext ?? WebSessionsContext(account: context.account)
|
||||
|
||||
webSessionsContext.loadMore()
|
||||
|
||||
let updateTwoStepAuthDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updateTwoStepAuthDisposable)
|
||||
|
||||
@ -670,7 +674,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
pushControllerImpl?(controller, true)
|
||||
}
|
||||
}, openActiveSessions: {
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext), true)
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: true), true)
|
||||
}, setupAccountAutoremove: {
|
||||
let signal = privacySettingsPromise.get()
|
||||
|> take(1)
|
||||
@ -740,8 +744,8 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
updatedSettings?(settings)
|
||||
}))
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), privacySettingsPromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), recentPeers(account: context.account), blockedPeersContext.state, activeSessionsContext.state, context.sharedContext.accountManager.accessChallengeData(), twoStepAuthDataValue.get())
|
||||
|> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeSessionsState, accessChallengeData, twoStepAuthData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), privacySettingsPromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), recentPeers(account: context.account), blockedPeersContext.state, webSessionsContext.state, context.sharedContext.accountManager.accessChallengeData(), twoStepAuthDataValue.get())
|
||||
|> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuthData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var rightNavigationButton: ItemListNavigationButton?
|
||||
if privacySettings == nil || state.updatingAccountTimeoutValue != nil {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
@ -749,7 +753,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PrivacySettings_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeSessionsCount: activeSessionsState.sessions.count, twoStepAuthData: twoStepAuthData), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, twoStepAuthData: twoStepAuthData), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - labelLayout.size.width - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
@ -447,7 +447,7 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
|
||||
return entries
|
||||
}
|
||||
|
||||
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext) -> ViewController {
|
||||
public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext, websitesOnly: Bool) -> ViewController {
|
||||
let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: RecentSessionsControllerState())
|
||||
let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in
|
||||
@ -459,6 +459,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -468,8 +469,23 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||
let terminateOtherSessionsDisposable = MetaDisposable()
|
||||
actionsDisposable.add(terminateOtherSessionsDisposable)
|
||||
|
||||
let mode = ValuePromise<RecentSessionsMode>(.sessions)
|
||||
let websitesPromise = Promise<([WebAuthorization], [PeerId : Peer])?>(nil)
|
||||
let didAppearValue = ValuePromise<Bool>(false)
|
||||
|
||||
if websitesOnly {
|
||||
let autoDismissDisposable = (webSessionsContext.state
|
||||
|> filter { !$0.isLoadingMore && $0.sessions.isEmpty }
|
||||
|> take(1)
|
||||
|> mapToSignal { _ in
|
||||
return didAppearValue.get()
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
dismissImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
let mode = ValuePromise<RecentSessionsMode>(websitesOnly ? .websites : .sessions)
|
||||
|
||||
let arguments = RecentSessionsControllerArguments(context: context, setSessionIdWithRevealedOptions: { sessionId, fromSessionId in
|
||||
updateState { state in
|
||||
@ -636,16 +652,11 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||
}
|
||||
|
||||
let emptyStateItem: ItemListControllerEmptyStateItem? = nil
|
||||
/*if sessionsState.sessions.isEmpty {
|
||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||
} else if sessionsState.sessions.count == 1 && mode == .sessions {
|
||||
emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
|
||||
}*/
|
||||
|
||||
let title: ItemListControllerTitle
|
||||
let entries: [RecentSessionsEntry]
|
||||
if !websites.isEmpty {
|
||||
title = .sectionControl([presentationData.strings.AuthSessions_Sessions, presentationData.strings.AuthSessions_LoggedIn], mode.rawValue)
|
||||
if websitesOnly {
|
||||
title = .text(presentationData.strings.AuthSessions_LoggedIn)
|
||||
} else {
|
||||
title = .text(presentationData.strings.AuthSessions_DevicesTitle)
|
||||
}
|
||||
@ -678,6 +689,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||
controller.titleControlValueChanged = { [weak mode] index in
|
||||
mode?.set(index == 0 ? .sessions : .websites)
|
||||
}
|
||||
controller.didAppear = { _ in
|
||||
didAppearValue.set(true)
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
@ -686,6 +700,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -545,7 +545,7 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
|
||||
present(.push, twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: true, data: nil)))
|
||||
}),
|
||||
SettingsSearchableItem(id: .privacy(9), title: strings.PrivacySettings_AuthSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: ActiveSessionsContext(account: context.account), webSessionsContext: WebSessionsContext(account: context.account)))
|
||||
present(.push, recentSessionsController(context: context, activeSessionsContext: ActiveSessionsContext(account: context.account), webSessionsContext: WebSessionsContext(account: context.account), websitesOnly: true))
|
||||
}),
|
||||
SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_DeleteAccountTitle, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
|
||||
presentPrivacySettings(context, present, .accountTimeout)
|
||||
|
@ -670,10 +670,8 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
|
||||
|
||||
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
|
||||
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
|
||||
if enableQRLogin {
|
||||
if enableQRLogin || otherSessionCount != 0 {
|
||||
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionCount + 1)"))
|
||||
} else {
|
||||
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
||||
}
|
||||
|
||||
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
||||
@ -683,9 +681,7 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
|
||||
entries.append(.themes(presentationData.theme, PresentationResourcesSettings.appearance, presentationData.strings.Settings_Appearance))
|
||||
let languageName = presentationData.strings.primaryComponent.localizedName
|
||||
entries.append(.language(presentationData.theme, PresentationResourcesSettings.language, presentationData.strings.Settings_AppLanguage, languageName.isEmpty ? presentationData.strings.Localization_LanguageName : languageName))
|
||||
if enableQRLogin {
|
||||
entries.append(.contentStickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
||||
}
|
||||
entries.append(.contentStickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
|
||||
|
||||
if hasWallet {
|
||||
entries.append(.wallet(presentationData.theme, PresentationResourcesSettings.wallet, "Gram Wallet", ""))
|
||||
@ -1113,7 +1109,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
if count == 0 {
|
||||
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
||||
} else {
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext))
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -1267,6 +1263,14 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
}
|
||||
updatePassport()
|
||||
|
||||
let updateActiveSessions: () -> Void = {
|
||||
let _ = (activeSessionsContextAndCount.get()
|
||||
|> deliverOnMainQueue
|
||||
|> take(1)).start(next: { activeSessionsContext, _, _ in
|
||||
activeSessionsContext.loadMore()
|
||||
})
|
||||
}
|
||||
|
||||
let notificationsAuthorizationStatus = Promise<AccessType>(.allowed)
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
notificationsAuthorizationStatus.set(
|
||||
@ -1655,6 +1659,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
controller.didAppear = { _ in
|
||||
updatePassport()
|
||||
updateNotifyExceptions()
|
||||
updateActiveSessions()
|
||||
}
|
||||
controller.previewItemWithTag = { tag in
|
||||
if let tag = tag as? SettingsEntryTag, case let .account(id) = tag {
|
||||
|
@ -7,29 +7,43 @@ import TelegramApi
|
||||
public struct InactiveChannel : Equatable {
|
||||
public let peer: Peer
|
||||
public let lastActivityDate: Int32
|
||||
init(peer: Peer, lastActivityDate: Int32) {
|
||||
public let participantsCount: Int32?
|
||||
|
||||
init(peer: Peer, lastActivityDate: Int32, participantsCount: Int32?) {
|
||||
self.peer = peer
|
||||
self.lastActivityDate = lastActivityDate
|
||||
self.participantsCount = participantsCount
|
||||
}
|
||||
public static func ==(lhs: InactiveChannel, rhs: InactiveChannel) -> Bool {
|
||||
return lhs.peer.isEqual(rhs.peer) && lhs.lastActivityDate == rhs.lastActivityDate
|
||||
return lhs.peer.isEqual(rhs.peer) && lhs.lastActivityDate == rhs.lastActivityDate && lhs.participantsCount == rhs.participantsCount
|
||||
}
|
||||
}
|
||||
|
||||
public func inactiveChannelList(network: Network) -> Signal<[InactiveChannel], NoError> {
|
||||
return network.request(Api.functions.channels.getInactiveChannels())
|
||||
|> retryRequest
|
||||
|> map { result in
|
||||
switch result {
|
||||
case let .inactiveChats(dates, chats, users):
|
||||
let channels = chats.compactMap {
|
||||
parseTelegramGroupOrChannel(chat: $0)
|
||||
}
|
||||
var inactive: [InactiveChannel] = []
|
||||
for (i, channel) in channels.enumerated() {
|
||||
inactive.append(InactiveChannel(peer: channel, lastActivityDate: dates[i]))
|
||||
}
|
||||
return inactive
|
||||
|> retryRequest
|
||||
|> map { result in
|
||||
switch result {
|
||||
case let .inactiveChats(dates, chats, users):
|
||||
let channels = chats.compactMap {
|
||||
parseTelegramGroupOrChannel(chat: $0)
|
||||
}
|
||||
var participantsCounts: [PeerId: Int32] = [:]
|
||||
for chat in chats {
|
||||
switch chat {
|
||||
case let .channel(channel):
|
||||
if let participantsCountValue = channel.participantsCount {
|
||||
participantsCounts[chat.peerId] = channel.participantsCount
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
var inactive: [InactiveChannel] = []
|
||||
for (i, channel) in channels.enumerated() {
|
||||
inactive.append(InactiveChannel(peer: channel, lastActivityDate: dates[i], participantsCount: participantsCounts[channel.id]))
|
||||
}
|
||||
return inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ public func joinChannel(account: Account, peerId: PeerId) -> Signal<RenderedChan
|
||||
|> take(1)
|
||||
|> castError(JoinChannelError.self)
|
||||
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
|
||||
#if DEBUG
|
||||
return .fail(.tooMuchJoined)
|
||||
#endif
|
||||
if let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
|
||||
|> mapError { error -> JoinChannelError in
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import PeerInfoUI
|
||||
|
||||
private enum SubscriberAction {
|
||||
case join
|
||||
@ -118,40 +119,41 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
|
||||
switch action {
|
||||
case .join:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id)
|
||||
|> afterDisposed { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
strongSelf.activityIndicator.stopAnimating()
|
||||
}
|
||||
case .join:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id)
|
||||
|> afterDisposed { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
strongSelf.activityIndicator.stopAnimating()
|
||||
}
|
||||
}).start(error: { [weak self] error in
|
||||
guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let text: String
|
||||
switch error {
|
||||
case .tooMuchJoined:
|
||||
text = presentationInterfaceState.strings.Join_ChannelsTooMuch
|
||||
default:
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
text = presentationInterfaceState.strings.Channel_ErrorAccessDenied
|
||||
} else {
|
||||
text = presentationInterfaceState.strings.Group_ErrorAccessDenied
|
||||
}
|
||||
}
|
||||
strongSelf.interfaceInteraction?.presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationInterfaceState.strings.Common_OK, action: {})]), nil)
|
||||
}))
|
||||
case .kicked:
|
||||
break
|
||||
case .muteNotifications, .unmuteNotifications:
|
||||
if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer {
|
||||
self.actionDisposable.set(togglePeerMuted(account: context.account, peerId: peer.id).start())
|
||||
}
|
||||
}).start(error: { [weak self] error in
|
||||
guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let text: String
|
||||
switch error {
|
||||
case .tooMuchJoined:
|
||||
strongSelf.interfaceInteraction?.getNavigationController()?.pushViewController(oldChannelsController(context: context))
|
||||
return
|
||||
default:
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
text = presentationInterfaceState.strings.Channel_ErrorAccessDenied
|
||||
} else {
|
||||
text = presentationInterfaceState.strings.Group_ErrorAccessDenied
|
||||
}
|
||||
}
|
||||
strongSelf.interfaceInteraction?.presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationInterfaceState.strings.Common_OK, action: {})]), nil)
|
||||
}))
|
||||
case .kicked:
|
||||
break
|
||||
case .muteNotifications, .unmuteNotifications:
|
||||
if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer {
|
||||
self.actionDisposable.set(togglePeerMuted(account: context.account, peerId: peer.id).start())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,6 @@ final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode {
|
||||
if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad)
|
||||
self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user