Forum improvements

This commit is contained in:
Ali 2022-11-22 19:50:50 +04:00
parent dc085a2fe9
commit 80a7324668
30 changed files with 1076 additions and 443 deletions

View File

@ -8302,3 +8302,10 @@ Sorry for the inconvenience.";
"CreateTopic.ShowGeneral" = "Show in Topics"; "CreateTopic.ShowGeneral" = "Show in Topics";
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it."; "CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";
"ChatList.DeleteThreadsConfirmation_1" = "Delete";
"ChatList.DeleteThreadsConfirmation_any" = "Delete %@ Threads";
"ChatList.DeletedThreads_1" = "Deleted 1 thread";
"ChatList.DeletedThreads_any" = "Deleted %@ threads";
"DialogList.SearchSectionMessagesIn" = "Messages in %@";

View File

@ -21,7 +21,7 @@ public enum ChatListSearchItemHeaderType {
case chats case chats
case chatTypes case chatTypes
case faq case faq
case messages case messages(location: String?)
case groupMembers case groupMembers
case activeVoiceChats case activeVoiceChats
case recentCalls case recentCalls
@ -65,8 +65,12 @@ public enum ChatListSearchItemHeaderType {
return strings.ChatList_ChatTypesSection return strings.ChatList_ChatTypesSection
case .faq: case .faq:
return strings.Settings_FrequentlyAskedQuestions return strings.Settings_FrequentlyAskedQuestions
case .messages: case let .messages(location):
return strings.DialogList_SearchSectionMessages if let location {
return strings.DialogList_SearchSectionMessagesIn(location).string
} else {
return strings.DialogList_SearchSectionMessages
}
case .groupMembers: case .groupMembers:
return strings.Group_GroupMembersHeader return strings.Group_GroupMembersHeader
case .activeVoiceChats: case .activeVoiceChats:
@ -120,8 +124,12 @@ public enum ChatListSearchItemHeaderType {
return .chatTypes return .chatTypes
case .faq: case .faq:
return .faq return .faq
case .messages: case let .messages(location):
return .messages if let _ = location {
return .messagesWithLocation
} else {
return .messages
}
case .groupMembers: case .groupMembers:
return .groupMembers return .groupMembers
case .activeVoiceChats: case .activeVoiceChats:
@ -160,6 +168,7 @@ private enum ChatListSearchItemHeaderId: Int32 {
case chatTypes case chatTypes
case faq case faq
case messages case messages
case messagesWithLocation
case photos case photos
case links case links
case files case files

View File

@ -172,6 +172,11 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
if readCounters.isUnread { if readCounters.isUnread {
isUnread = true isUnread = true
} }
var isForum = false
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true
}
if case let .chatList(currentFilter) = source { if case let .chatList(currentFilter) = source {
if let currentFilter = currentFilter, case let .filter(id, title, emoticon, data) = currentFilter { if let currentFilter = currentFilter, case let .filter(id, title, emoticon, data) = currentFilter {
@ -317,7 +322,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start()
f(.default) f(.default)
}))) })))
} else { } else if !isForum {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in
let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start()
f(.default) f(.default)
@ -494,15 +499,12 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let strings = presentationData.strings let strings = presentationData.strings
return combineLatest( return context.engine.data.get(
context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
), TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)
)
) )
|> mapToSignal { peer, threadData -> Signal<[ContextMenuItem], NoError> in |> mapToSignal { peer, peerNotificationSettings, threadData -> Signal<[ContextMenuItem], NoError> in
guard case let .channel(channel) = peer else { guard case let .channel(channel) = peer else {
return .single([]) return .single([])
} }
@ -548,12 +550,19 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
} }
var isMuted = false var isMuted = false
if case .muted = threadData.notificationSettings.muteState { switch threadData.notificationSettings.muteState {
case .muted:
isMuted = true isMuted = true
case .unmuted:
isMuted = false
case .default:
if case .muted = peerNotificationSettings.muteState {
isMuted = true
}
} }
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] c, f in items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] c, f in
if isMuted { if isMuted {
let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: threadId) let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
f(.default) f(.default)
}) })
@ -776,7 +785,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
} }
private func openCustomMute(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, baseController: ViewController) { private func openCustomMute(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, baseController: ViewController) {
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, peerId: peerId, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if value <= 0 { if value <= 0 {

File diff suppressed because it is too large Load Diff

View File

@ -253,13 +253,17 @@ private final class ChatListShimmerNode: ASDisplayNode {
context.setFillColor(UIColor.clear.cgColor) context.setFillColor(UIColor.clear.cgColor)
if !isInlineMode { if !isInlineMode {
if itemNodes[sampleIndex].avatarNode.isHidden { if !itemNodes[sampleIndex].avatarNode.isHidden {
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
} }
} }
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) 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) let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
@ -1157,7 +1161,15 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let animationCache: AnimationCache private let animationCache: AnimationCache
private let animationRenderer: MultiAnimationRenderer private let animationRenderer: MultiAnimationRenderer
let containerNode: ChatListContainerNode let mainContainerNode: ChatListContainerNode
var effectiveContainerNode: ChatListContainerNode {
if let inlineStackContainerNode = self.inlineStackContainerNode {
return inlineStackContainerNode
} else {
return self.mainContainerNode
}
}
private(set) var inlineStackContainerTransitionFraction: CGFloat = 0.0 private(set) var inlineStackContainerTransitionFraction: CGFloat = 0.0
private(set) var inlineStackContainerNode: ChatListContainerNode? private(set) var inlineStackContainerNode: ChatListContainerNode?
@ -1206,7 +1218,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterBecameEmpty: ((ChatListFilter?) -> Void)?
var filterEmptyAction: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)?
var secondaryEmptyAction: (() -> Void)? var secondaryEmptyAction: (() -> Void)?
self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in self.mainContainerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in
filterBecameEmpty?(filter) filterBecameEmpty?(filter)
}, filterEmptyAction: { filter in }, filterEmptyAction: { filter in
filterEmptyAction?(filter) filterEmptyAction?(filter)
@ -1224,12 +1236,12 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.backgroundColor = presentationData.theme.chatList.backgroundColor self.backgroundColor = presentationData.theme.chatList.backgroundColor
self.addSubnode(self.containerNode) self.addSubnode(self.mainContainerNode)
self.containerNode.contentOffsetChanged = { [weak self] offset in self.mainContainerNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged(offset: offset, isPrimary: true) self?.contentOffsetChanged(offset: offset, isPrimary: true)
} }
self.containerNode.contentScrollingEnded = { [weak self] listView in self.mainContainerNode.contentScrollingEnded = { [weak self] listView in
return self?.contentScrollingEnded(listView: listView, isPrimary: true) ?? false return self?.contentScrollingEnded(listView: listView, isPrimary: true) ?? false
} }
@ -1259,7 +1271,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
(controller.navigationController as? NavigationController)?.replaceController(controller, with: chatController, animated: false) (controller.navigationController as? NavigationController)?.replaceController(controller, with: chatController, animated: false)
} }
self.containerNode.onFilterSwitch = { [weak self] in self.mainContainerNode.onFilterSwitch = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controller?.dismissAllUndoControllers() strongSelf.controller?.dismissAllUndoControllers()
} }
@ -1362,7 +1374,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.containerNode.updatePresentationData(presentationData) self.mainContainerNode.updatePresentationData(presentationData)
self.inlineStackContainerNode?.updatePresentationData(presentationData) self.inlineStackContainerNode?.updatePresentationData(presentationData)
self.searchDisplayController?.updatePresentationData(presentationData) self.searchDisplayController?.updatePresentationData(presentationData)
@ -1432,7 +1444,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
childrenLayout.intrinsicInsets = UIEdgeInsets(top: visualNavigationHeight, left: childrenLayout.intrinsicInsets.left, bottom: childrenLayout.intrinsicInsets.bottom, right: childrenLayout.intrinsicInsets.right) childrenLayout.intrinsicInsets = UIEdgeInsets(top: visualNavigationHeight, left: childrenLayout.intrinsicInsets.left, bottom: childrenLayout.intrinsicInsets.bottom, right: childrenLayout.intrinsicInsets.right)
self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.mainContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
var mainNavigationBarHeight = navigationBarHeight var mainNavigationBarHeight = navigationBarHeight
var cleanMainNavigationBarHeight = cleanNavigationBarHeight var cleanMainNavigationBarHeight = cleanNavigationBarHeight
var mainInsets = insets var mainInsets = insets
@ -1441,13 +1453,13 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
cleanMainNavigationBarHeight = visualNavigationHeight cleanMainNavigationBarHeight = visualNavigationHeight
mainInsets.top = visualNavigationHeight mainInsets.top = visualNavigationHeight
} }
self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, inlineNavigationTransitionFraction: self.inlineStackContainerTransitionFraction, transition: transition) self.mainContainerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, inlineNavigationTransitionFraction: self.inlineStackContainerTransitionFraction, transition: transition)
if let inlineStackContainerNode = self.inlineStackContainerNode { if let inlineStackContainerNode = self.inlineStackContainerNode {
var inlineStackContainerNodeTransition = transition var inlineStackContainerNodeTransition = transition
var animateIn = false var animateIn = false
if inlineStackContainerNode.supernode == nil { if inlineStackContainerNode.supernode == nil {
self.insertSubnode(inlineStackContainerNode, aboveSubnode: self.containerNode) self.insertSubnode(inlineStackContainerNode, aboveSubnode: self.mainContainerNode)
inlineStackContainerNodeTransition = .immediate inlineStackContainerNodeTransition = .immediate
animateIn = true animateIn = true
} }
@ -1486,9 +1498,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
let effectiveLocation = self.inlineStackContainerNode?.location ?? self.location let effectiveLocation = self.inlineStackContainerNode?.location ?? self.location
var filter: ChatListNodePeersFilter = [] let filter: ChatListNodePeersFilter = []
if case .forum = effectiveLocation { if case .forum = effectiveLocation {
filter.insert(.excludeRecent) //filter.insert(.excludeRecent)
} }
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in
@ -1515,7 +1527,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
requestDeactivateSearch() requestDeactivateSearch()
} }
}) })
self.containerNode.accessibilityElementsHidden = true self.mainContainerNode.accessibilityElementsHidden = true
self.inlineStackContainerNode?.accessibilityElementsHidden = true
return (contentNode.filterContainerNode, { [weak self] focus in return (contentNode.filterContainerNode, { [weak self] focus in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -1538,7 +1551,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
if let searchDisplayController = self.searchDisplayController { if let searchDisplayController = self.searchDisplayController {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil self.searchDisplayController = nil
self.containerNode.accessibilityElementsHidden = false self.mainContainerNode.accessibilityElementsHidden = false
self.inlineStackContainerNode?.accessibilityElementsHidden = false
return { [weak self] in return { [weak self] in
if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight) = strongSelf.containerLayout { if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight) = strongSelf.containerLayout {
@ -1551,7 +1565,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
func clearHighlightAnimated(_ animated: Bool) { func clearHighlightAnimated(_ animated: Bool) {
self.containerNode.currentItemNode.clearHighlightAnimated(true) self.mainContainerNode.currentItemNode.clearHighlightAnimated(true)
self.inlineStackContainerNode?.currentItemNode.clearHighlightAnimated(true) self.inlineStackContainerNode?.currentItemNode.clearHighlightAnimated(true)
} }
@ -1581,7 +1595,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
if isPrimary { if isPrimary {
targetNode = inlineStackContainerNode targetNode = inlineStackContainerNode
} else { } else {
targetNode = self.containerNode targetNode = self.mainContainerNode
} }
switch offset { switch offset {
@ -1626,24 +1640,27 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
return self.contentScrollingEnded?(listView) ?? false return self.contentScrollingEnded?(listView) ?? false
} }
func setInlineChatList(location: ChatListControllerLocation?) { func makeInlineChatList(location: ChatListControllerLocation) -> ChatListContainerNode {
if let location = location { let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {})
if self.inlineStackContainerNode?.location != location { return inlineStackContainerNode
let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {}) }
func setInlineChatList(inlineStackContainerNode: ChatListContainerNode?) {
if let inlineStackContainerNode = inlineStackContainerNode {
if self.inlineStackContainerNode !== inlineStackContainerNode {
inlineStackContainerNode.leftSeparatorLayer.isHidden = false inlineStackContainerNode.leftSeparatorLayer.isHidden = false
inlineStackContainerNode.presentAlert = self.containerNode.presentAlert inlineStackContainerNode.presentAlert = self.mainContainerNode.presentAlert
inlineStackContainerNode.present = self.containerNode.present inlineStackContainerNode.present = self.mainContainerNode.present
inlineStackContainerNode.push = self.containerNode.push inlineStackContainerNode.push = self.mainContainerNode.push
inlineStackContainerNode.deletePeerChat = self.containerNode.deletePeerChat inlineStackContainerNode.deletePeerChat = self.mainContainerNode.deletePeerChat
inlineStackContainerNode.deletePeerThread = self.containerNode.deletePeerThread inlineStackContainerNode.deletePeerThread = self.mainContainerNode.deletePeerThread
inlineStackContainerNode.setPeerThreadStopped = self.containerNode.setPeerThreadStopped inlineStackContainerNode.setPeerThreadStopped = self.mainContainerNode.setPeerThreadStopped
inlineStackContainerNode.setPeerThreadPinned = self.containerNode.setPeerThreadPinned inlineStackContainerNode.setPeerThreadPinned = self.mainContainerNode.setPeerThreadPinned
inlineStackContainerNode.setPeerThreadHidden = self.containerNode.setPeerThreadHidden inlineStackContainerNode.setPeerThreadHidden = self.mainContainerNode.setPeerThreadHidden
inlineStackContainerNode.peerSelected = self.containerNode.peerSelected inlineStackContainerNode.peerSelected = self.mainContainerNode.peerSelected
inlineStackContainerNode.groupSelected = self.containerNode.groupSelected inlineStackContainerNode.groupSelected = self.mainContainerNode.groupSelected
inlineStackContainerNode.updatePeerGrouping = self.containerNode.updatePeerGrouping inlineStackContainerNode.updatePeerGrouping = self.mainContainerNode.updatePeerGrouping
inlineStackContainerNode.contentOffsetChanged = { [weak self] offset in inlineStackContainerNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged(offset: offset, isPrimary: false) self?.contentOffsetChanged(offset: offset, isPrimary: false)
@ -1652,9 +1669,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
return self?.contentScrollingEnded(listView: listView, isPrimary: false) ?? false return self?.contentScrollingEnded(listView: listView, isPrimary: false) ?? false
} }
inlineStackContainerNode.activateChatPreview = self.containerNode.activateChatPreview inlineStackContainerNode.activateChatPreview = self.mainContainerNode.activateChatPreview
inlineStackContainerNode.addedVisibleChatsWithPeerIds = self.containerNode.addedVisibleChatsWithPeerIds inlineStackContainerNode.addedVisibleChatsWithPeerIds = self.mainContainerNode.addedVisibleChatsWithPeerIds
inlineStackContainerNode.didBeginSelectingChats = nil inlineStackContainerNode.didBeginSelectingChats = self.mainContainerNode.didBeginSelectingChats
inlineStackContainerNode.displayFilterLimit = nil inlineStackContainerNode.displayFilterLimit = nil
let previousInlineStackContainerNode = self.inlineStackContainerNode let previousInlineStackContainerNode = self.inlineStackContainerNode
@ -1663,7 +1680,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.inlineStackContainerTransitionFraction = 1.0 self.inlineStackContainerTransitionFraction = 1.0
if let _ = self.containerLayout { if let _ = self.containerLayout {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
if let previousInlineStackContainerNode { if let previousInlineStackContainerNode {
transition.updatePosition(node: previousInlineStackContainerNode, position: CGPoint(x: previousInlineStackContainerNode.position.x + previousInlineStackContainerNode.bounds.width + UIScreenPixel, y: previousInlineStackContainerNode.position.y), completion: { [weak previousInlineStackContainerNode] _ in transition.updatePosition(node: previousInlineStackContainerNode, position: CGPoint(x: previousInlineStackContainerNode.position.x + previousInlineStackContainerNode.bounds.width + UIScreenPixel, y: previousInlineStackContainerNode.position.y), completion: { [weak previousInlineStackContainerNode] _ in
@ -1681,7 +1698,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.inlineStackContainerNode = nil self.inlineStackContainerNode = nil
self.inlineStackContainerTransitionFraction = 0.0 self.inlineStackContainerTransitionFraction = 0.0
self.containerNode.contentScrollingEnded = self.contentScrollingEnded self.mainContainerNode.contentScrollingEnded = self.contentScrollingEnded
if let _ = self.containerLayout { if let _ = self.containerLayout {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
@ -1701,7 +1718,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
func playArchiveAnimation() { func playArchiveAnimation() {
self.containerNode.playArchiveAnimation() self.mainContainerNode.playArchiveAnimation()
} }
func scrollToTop() { func scrollToTop() {
@ -1710,7 +1727,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} else if let inlineStackContainerNode = self.inlineStackContainerNode { } else if let inlineStackContainerNode = self.inlineStackContainerNode {
inlineStackContainerNode.scrollToTop() inlineStackContainerNode.scrollToTop()
} else { } else {
self.containerNode.scrollToTop() self.mainContainerNode.scrollToTop()
} }
} }
} }

View File

@ -136,6 +136,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
var initialFilter = initialFilter
if case .chats = initialFilter, case .forum = location {
initialFilter = .topics
}
self.context = context self.context = context
self.peersFilter = filter self.peersFilter = filter
self.location = location self.location = location

View File

@ -720,7 +720,11 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
openClearRecentlyDownloaded() openClearRecentlyDownloaded()
}) })
case .index: case .index:
header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) var headerType: ChatListSearchItemHeaderType = .messages(location: nil)
if case .forum = location, let peer = peer.peer {
headerType = .messages(location: peer.compactDisplayTitle)
}
header = ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
} }
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
var isMedia = false var isMedia = false
@ -980,7 +984,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var peersFilter = peersFilter var peersFilter = peersFilter
if case .forum = location { if case .forum = location {
peersFilter.insert(.excludeRecent) //peersFilter.insert(.excludeRecent)
} else if case .chatList(.archive) = location { } else if case .chatList(.archive) = location {
peersFilter.insert(.excludeRecent) peersFilter.insert(.excludeRecent)
} }
@ -1106,7 +1110,21 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let previousRecentlySearchedPeerOrder = Atomic<[EnginePeer.Id]>(value: []) let previousRecentlySearchedPeerOrder = Atomic<[EnginePeer.Id]>(value: [])
let fixedRecentlySearchedPeers: Signal<[RecentlySearchedPeer], NoError> let fixedRecentlySearchedPeers: Signal<[RecentlySearchedPeer], NoError>
if case .chats = key, case .chatList(.root) = location {
var enableRecentlySearched = false
if !self.peersFilter.contains(.excludeRecent) {
if case .chats = key {
if case .chatList(.root) = location {
enableRecentlySearched = true
} else if case .forum = location {
enableRecentlySearched = true
}
} else if case .topics = key, case .forum = location {
enableRecentlySearched = true
}
}
if enableRecentlySearched {
fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers()
|> map { peers -> [RecentlySearchedPeer] in |> map { peers -> [RecentlySearchedPeer] in
var result: [RecentlySearchedPeer] = [] var result: [RecentlySearchedPeer] = []
@ -1283,7 +1301,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1)
let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>), NoError> let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>), NoError>
if let query = query, case .chats = key { if let query = query, (key == .chats || key == .topics) {
let fixedOrRemovedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() let fixedOrRemovedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers()
|> map { peers -> [RecentlySearchedPeer] in |> map { peers -> [RecentlySearchedPeer] in
let allIds = peers.map(\.peer.peerId) let allIds = peers.map(\.peer.peerId)
@ -3071,6 +3089,11 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
}, 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 }, 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() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in })
var isInlineMode = false
if case .topics = key {
isInlineMode = true
}
interaction.isInlineMode = isInlineMode
let items = (0 ..< 2).compactMap { _ -> ListViewItem? in let items = (0 ..< 2).compactMap { _ -> ListViewItem? in
switch key { switch key {
@ -3277,17 +3300,36 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
let selectionOffset: CGFloat = hasSelection ? 45.0 : 0.0 let selectionOffset: CGFloat = hasSelection ? 45.0 : 0.0
if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode { if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode {
context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) if !isInlineMode {
if !itemNode.avatarNode.isHidden {
context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
}
}
let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY) let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) 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)
}
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNode.titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) let textFrame = itemNode.textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) if isInlineMode {
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) 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(itemNode.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 = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY) let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: floor(dateFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 30.0) 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(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
context.setBlendMode(.normal) context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor) context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)

View File

@ -58,32 +58,31 @@ func chatListSelectionOptions(context: AccountContext, peerIds: Set<PeerId>, fil
} }
func forumSelectionOptions(context: AccountContext, peerId: PeerId, threadIds: Set<Int64>, canDelete: Bool) -> Signal<ChatListSelectionOptions, NoError> { func forumSelectionOptions(context: AccountContext, peerId: PeerId, threadIds: Set<Int64>) -> Signal<ChatListSelectionOptions, NoError> {
let threadIdsArray = Array(threadIds) return context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
var threadSignals: [Signal<MessageHistoryThreadData?, NoError>] = [] EngineDataList(threadIds.map { TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: $0) })
for threadId in threadIdsArray { )
threadSignals.append( |> map { peer, threadDatas -> ChatListSelectionOptions in
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)) guard !threadIds.isEmpty, case let .channel(channel) = peer else {
)
}
return combineLatest(threadSignals)
|> map { threadDatas -> ChatListSelectionOptions in
if threadIds.isEmpty {
return ChatListSelectionOptions(read: .selective(enabled: false), delete: false) return ChatListSelectionOptions(read: .selective(enabled: false), delete: false)
} else {
var hasUnread = false
for thread in threadDatas {
guard let thread = thread else {
continue
}
if thread.incomingUnreadCount > 0 {
hasUnread = true
break
}
}
return ChatListSelectionOptions(read: .selective(enabled: hasUnread), delete: canDelete)
} }
var canDelete = !threadIds.contains(1)
if !channel.hasPermission(.deleteAllMessages) {
canDelete = false
}
var hasUnread = false
for thread in threadDatas {
guard let thread = thread else {
continue
}
if thread.incomingUnreadCount > 0 {
hasUnread = true
break
}
}
return ChatListSelectionOptions(read: .selective(enabled: hasUnread), delete: canDelete)
} }
} }

View File

@ -104,8 +104,12 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
switch self.index { switch self.index {
case let .chatList(index): case let .chatList(index):
return index.pinningIndex != nil return index.pinningIndex != nil
case .forum: case let .forum(pinnedIndex, _, _, _, _):
return false if case .index = pinnedIndex {
return true
} else {
return false
}
} }
} }
@ -1349,9 +1353,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateAlpha(layer: compoundHighlightingNode.layer, alpha: self.authorNode.alpha) transition.updateAlpha(layer: compoundHighlightingNode.layer, alpha: self.authorNode.alpha)
} }
if let item = self.item, case let .chatList(index) = item.index { if let item = self.item {
let onlineIcon: UIImage? let onlineIcon: UIImage?
if index.pinningIndex != nil { if item.isPinned {
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat) onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat)
} else { } else {
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat) onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat)
@ -2015,7 +2019,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundReactions(item.presentationData.theme, diameter: badgeDiameter) currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundReactions(item.presentationData.theme, diameter: badgeDiameter)
} }
mentionBadgeContent = .mention mentionBadgeContent = .mention
} else if case let .chatList(chatListIndex) = item.index, chatListIndex.pinningIndex != nil, promoInfo == nil, currentBadgeBackgroundImage == nil { } else if item.isPinned, promoInfo == nil, currentBadgeBackgroundImage == nil {
currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter) currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter)
} }
} }
@ -2896,6 +2900,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let compoundTextButtonNode = strongSelf.compoundTextButtonNode { if let compoundTextButtonNode = strongSelf.compoundTextButtonNode {
if strongSelf.textNode.textNode.supernode !== compoundTextButtonNode { if strongSelf.textNode.textNode.supernode !== compoundTextButtonNode {
compoundTextButtonNode.addSubnode(strongSelf.textNode.textNode) compoundTextButtonNode.addSubnode(strongSelf.textNode.textNode)
if let dustNode = strongSelf.dustNode {
compoundTextButtonNode.addSubnode(dustNode)
}
} }
strongSelf.textNode.textNode.frame = textNodeFrame.offsetBy(dx: -compoundTextButtonNode.frame.minX, dy: -compoundTextButtonNode.frame.minY) strongSelf.textNode.textNode.frame = textNodeFrame.offsetBy(dx: -compoundTextButtonNode.frame.minX, dy: -compoundTextButtonNode.frame.minY)
@ -2903,6 +2910,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
if strongSelf.textNode.textNode.supernode !== strongSelf.mainContentContainerNode { if strongSelf.textNode.textNode.supernode !== strongSelf.mainContentContainerNode {
strongSelf.mainContentContainerNode.addSubnode(strongSelf.textNode.textNode) strongSelf.mainContentContainerNode.addSubnode(strongSelf.textNode.textNode)
if let dustNode = strongSelf.dustNode {
strongSelf.mainContentContainerNode.addSubnode(dustNode)
}
} }
strongSelf.textNode.textNode.frame = textNodeFrame strongSelf.textNode.textNode.frame = textNodeFrame
@ -2917,7 +2927,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
dustNode = InvisibleInkDustNode(textNode: nil) dustNode = InvisibleInkDustNode(textNode: nil)
dustNode.isUserInteractionEnabled = false dustNode.isUserInteractionEnabled = false
strongSelf.dustNode = dustNode strongSelf.dustNode = dustNode
strongSelf.mainContentContainerNode.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode)
strongSelf.textNode.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode)
} }
dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)

View File

@ -469,10 +469,17 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
var isForum = false var isForum = false
if let peer = chatPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) { if let peer = chatPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true isForum = true
if editing { if editing, case .chatList = mode {
enabled = false enabled = false
} }
} }
var selectable = editing
if case .chatList = mode {
if isForum {
selectable = false
}
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
@ -483,7 +490,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
peer: peerContent, peer: peerContent,
status: status, status: status,
enabled: enabled, enabled: enabled,
selection: editing && !isForum ? .selectable(selected: selected) : .none, selection: selectable ? .selectable(selected: selected) : .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil, index: nil,
header: header, header: header,
@ -644,11 +651,18 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
var isForum = false var isForum = false
if let peer = chatPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) { if let peer = chatPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true isForum = true
if editing { if editing, case .chatList = mode {
enabled = false enabled = false
} }
} }
var selectable = editing
if case .chatList = mode {
if isForum {
selectable = false
}
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem( return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
sortOrder: presentationData.nameSortOrder, sortOrder: presentationData.nameSortOrder,
@ -658,7 +672,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
peer: peerContent, peer: peerContent,
status: status, status: status,
enabled: enabled, enabled: enabled,
selection: editing && !isForum ? .selectable(selected: selected) : .none, selection: selectable ? .selectable(selected: selected) : .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil, index: nil,
header: header, header: header,

View File

@ -1631,7 +1631,7 @@ open class NavigationBar: ASDisplayNode {
if self.secondaryContentNode !== secondaryContentNode { if self.secondaryContentNode !== secondaryContentNode {
if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode { if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode {
if animated { if animated {
previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previous] finished in previous.layer.animateAlpha(from: previous.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previous] finished in
if finished { if finished {
previous?.removeFromSupernode() previous?.removeFromSupernode()
previous?.layer.removeAllAnimations() previous?.layer.removeAllAnimations()
@ -1646,7 +1646,7 @@ open class NavigationBar: ASDisplayNode {
self.clippingNode.addSubnode(secondaryContentNode) self.clippingNode.addSubnode(secondaryContentNode)
if animated { if animated {
secondaryContentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) secondaryContentNode.layer.animateAlpha(from: 0.0, to: secondaryContentNode.alpha, duration: 0.3)
} }
} }
} }

View File

@ -107,6 +107,7 @@ swift_library(
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard", "//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
"//submodules/PersistentStringHash:PersistentStringHash", "//submodules/PersistentStringHash:PersistentStringHash",
"//submodules/TelegramUI/Components/NotificationPeerExceptionController", "//submodules/TelegramUI/Components/NotificationPeerExceptionController",
"//submodules/TelegramUI/Components/ChatTimerScreen",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -18,6 +18,7 @@ import UndoUI
import PremiumUI import PremiumUI
import AuthorizationUI import AuthorizationUI
import AuthenticationServices import AuthenticationServices
import ChatTimerScreen
private final class PrivacyAndSecurityControllerArguments { private final class PrivacyAndSecurityControllerArguments {
let account: Account let account: Account
@ -34,10 +35,11 @@ private final class PrivacyAndSecurityControllerArguments {
let openActiveSessions: () -> Void let openActiveSessions: () -> Void
let toggleArchiveAndMuteNonContacts: (Bool) -> Void let toggleArchiveAndMuteNonContacts: (Bool) -> Void
let setupAccountAutoremove: () -> Void let setupAccountAutoremove: () -> Void
let setupMessageAutoremove: () -> Void
let openDataSettings: () -> Void let openDataSettings: () -> Void
let openEmailSettings: (String?) -> Void let openEmailSettings: (String?) -> Void
init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void) { init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, setupMessageAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void) {
self.account = account self.account = account
self.openBlockedUsers = openBlockedUsers self.openBlockedUsers = openBlockedUsers
self.openLastSeenPrivacy = openLastSeenPrivacy self.openLastSeenPrivacy = openLastSeenPrivacy
@ -52,6 +54,7 @@ private final class PrivacyAndSecurityControllerArguments {
self.openActiveSessions = openActiveSessions self.openActiveSessions = openActiveSessions
self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts
self.setupAccountAutoremove = setupAccountAutoremove self.setupAccountAutoremove = setupAccountAutoremove
self.setupMessageAutoremove = setupMessageAutoremove
self.openDataSettings = openDataSettings self.openDataSettings = openDataSettings
self.openEmailSettings = openEmailSettings self.openEmailSettings = openEmailSettings
} }
@ -62,11 +65,13 @@ private enum PrivacyAndSecuritySection: Int32 {
case privacy case privacy
case autoArchive case autoArchive
case account case account
case messageAutoremove
case dataSettings case dataSettings
} }
public enum PrivacyAndSecurityEntryTag: ItemListItemTag { public enum PrivacyAndSecurityEntryTag: ItemListItemTag {
case accountTimeout case accountTimeout
case messageAutoremoveTimeout
case autoArchive case autoArchive
public func isEqual(to other: ItemListItemTag) -> Bool { public func isEqual(to other: ItemListItemTag) -> Bool {
@ -100,6 +105,9 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case accountHeader(PresentationTheme, String) case accountHeader(PresentationTheme, String)
case accountTimeout(PresentationTheme, String, String) case accountTimeout(PresentationTheme, String, String)
case accountInfo(PresentationTheme, String) case accountInfo(PresentationTheme, String)
case messageAutoremoveHeader(PresentationTheme, String)
case messageAutoremoveTimeout(PresentationTheme, String, String)
case messageAutoremoveInfo(PresentationTheme, String)
case dataSettings(PresentationTheme, String) case dataSettings(PresentationTheme, String)
case dataSettingsInfo(PresentationTheme, String) case dataSettingsInfo(PresentationTheme, String)
@ -113,6 +121,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return PrivacyAndSecuritySection.autoArchive.rawValue return PrivacyAndSecuritySection.autoArchive.rawValue
case .accountHeader, .accountTimeout, .accountInfo: case .accountHeader, .accountTimeout, .accountInfo:
return PrivacyAndSecuritySection.account.rawValue return PrivacyAndSecuritySection.account.rawValue
case .messageAutoremoveHeader, .messageAutoremoveTimeout, .messageAutoremoveInfo:
return PrivacyAndSecuritySection.messageAutoremove.rawValue
case .dataSettings, .dataSettingsInfo: case .dataSettings, .dataSettingsInfo:
return PrivacyAndSecuritySection.dataSettings.rawValue return PrivacyAndSecuritySection.dataSettings.rawValue
} }
@ -162,10 +172,16 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return 20 return 20
case .accountInfo: case .accountInfo:
return 21 return 21
case .dataSettings: case .messageAutoremoveHeader:
return 22 return 22
case .dataSettingsInfo: case .messageAutoremoveTimeout:
return 23 return 23
case .messageAutoremoveInfo:
return 24
case .dataSettings:
return 25
case .dataSettingsInfo:
return 26
} }
} }
@ -297,6 +313,24 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .messageAutoremoveHeader(lhsTheme, lhsText):
if case let .messageAutoremoveHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .messageAutoremoveTimeout(lhsTheme, lhsText, lhsValue):
if case let .messageAutoremoveTimeout(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .messageAutoremoveInfo(lhsTheme, lhsText):
if case let .messageAutoremoveInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .dataSettings(lhsTheme, lhsText): case let .dataSettings(lhsTheme, lhsText):
if case let .dataSettings(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .dataSettings(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -389,6 +423,14 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
}, tag: PrivacyAndSecurityEntryTag.accountTimeout) }, tag: PrivacyAndSecurityEntryTag.accountTimeout)
case let .accountInfo(_, text): case let .accountInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .messageAutoremoveHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .messageAutoremoveTimeout(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.setupMessageAutoremove()
}, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout)
case let .messageAutoremoveInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .dataSettings(_, text): case let .dataSettings(_, text):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openDataSettings() arguments.openDataSettings()
@ -402,6 +444,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
private struct PrivacyAndSecurityControllerState: Equatable { private struct PrivacyAndSecurityControllerState: Equatable {
var updatingAccountTimeoutValue: Int32? = nil var updatingAccountTimeoutValue: Int32? = nil
var updatingAutomaticallyArchiveAndMuteNonContacts: Bool? = nil var updatingAutomaticallyArchiveAndMuteNonContacts: Bool? = nil
var updatingMessageAutoremoveTimeoutValue: Int32? = nil
} }
private func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> Int { private func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> Int {
@ -545,6 +588,27 @@ private func privacyAndSecurityControllerEntries(
} }
entries.append(.accountInfo(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountHelp)) entries.append(.accountInfo(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountHelp))
//TODO:localize
entries.append(.messageAutoremoveHeader(presentationData.theme, "MESSAGE TIMER"))
if let privacySettings = privacySettings {
let value: Int32?
if let updatingMessageAutoremoveTimeoutValue = state.updatingMessageAutoremoveTimeoutValue {
value = updatingMessageAutoremoveTimeoutValue
} else {
value = privacySettings.messageAutoremoveTimeout
}
let valueText: String
if let value {
valueText = timeIntervalString(strings: presentationData.strings, value: value)
} else {
valueText = "Never"
}
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", valueText))
} else {
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", presentationData.strings.Channel_NotificationLoading))
}
entries.append(.messageAutoremoveInfo(presentationData.theme, "Set a global message timer."))
entries.append(.dataSettings(presentationData.theme, presentationData.strings.PrivacySettings_DataSettings)) entries.append(.dataSettings(presentationData.theme, presentationData.strings.PrivacySettings_DataSettings))
entries.append(.dataSettingsInfo(presentationData.theme, presentationData.strings.PrivacySettings_DataSettingsHelp)) entries.append(.dataSettingsInfo(presentationData.theme, presentationData.strings.PrivacySettings_DataSettingsHelp))
@ -680,7 +744,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -703,7 +767,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -740,7 +804,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -763,7 +827,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -786,7 +850,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -809,7 +873,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, phoneDiscoveryEnabled: updatedDiscoveryEnabled ?? value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, phoneDiscoveryEnabled: updatedDiscoveryEnabled ?? value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -839,7 +903,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -912,7 +976,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: archiveValue, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: archiveValue, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -951,7 +1015,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: timeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: timeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout)))
} }
return .complete() return .complete()
} }
@ -999,6 +1063,85 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
presentControllerImpl?(controller) presentControllerImpl?(controller)
} }
})) }))
}, setupMessageAutoremove: {
let signal = privacySettingsPromise.get()
|> take(1)
|> deliverOnMainQueue
updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable] privacySettingsValue in
if let privacySettingsValue = privacySettingsValue {
let timeoutAction: (Int32?) -> Void = { timeout in
if let updateAccountTimeoutDisposable = updateAccountTimeoutDisposable {
updateState { state in
var state = state
state.updatingMessageAutoremoveTimeoutValue = timeout
return state
}
let applyTimeout: Signal<Void, NoError> = privacySettingsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in
if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: timeout)))
}
return .complete()
}
updateAccountTimeoutDisposable.set((context.engine.privacy.updateGlobalMessageRemovalTimeout(timeout: timeout)
|> then(applyTimeout)
|> deliverOnMainQueue).start(completed: {
updateState { state in
var state = state
state.updatingMessageAutoremoveTimeoutValue = nil
return state
}
}))
}
}
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: privacySettingsValue.messageAutoremoveTimeout, dismissByTapOutside: true, completion: { value in
timeoutAction(value)
})
presentControllerImpl?(controller)
/*
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
let timeoutValues: [Int32] = [
1 * 30 * 24 * 60 * 60,
3 * 30 * 24 * 60 * 60,
6 * 30 * 24 * 60 * 60,
365 * 24 * 60 * 60
]
var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in
return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value), action: {
dismissAction()
timeoutAction(value)
})
}
timeoutItems.append(ActionSheetButtonItem(title: presentationData.strings.PrivacySettings_DeleteAccountNow, color: .destructive, action: {
dismissAction()
guard let navigationController = getNavigationControllerImpl?() else {
return
}
let _ = (combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get())
|> take(1)
|> deliverOnMainQueue).start(next: { hasTwoStepAuth, twoStepAuthData in
let optionsController = deleteAccountOptionsController(context: context, navigationController: navigationController, hasTwoStepAuth: hasTwoStepAuth ?? false, twoStepAuthData: twoStepAuthData)
pushControllerImpl?(optionsController, true)
})
}))
controller.setItemGroups([
ActionSheetItemGroup(items: timeoutItems),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller)*/
}
}))
}, openDataSettings: { }, openDataSettings: {
pushControllerImpl?(dataPrivacyController(context: context), true) pushControllerImpl?(dataPrivacyController(context: context), true)
}, openEmailSettings: { emailPattern in }, openEmailSettings: { emailPattern in

View File

@ -180,6 +180,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[383348795] = { return Api.ContactStatus.parse_contactStatus($0) } dict[383348795] = { return Api.ContactStatus.parse_contactStatus($0) }
dict[2104790276] = { return Api.DataJSON.parse_dataJSON($0) } dict[2104790276] = { return Api.DataJSON.parse_dataJSON($0) }
dict[414687501] = { return Api.DcOption.parse_dcOption($0) } dict[414687501] = { return Api.DcOption.parse_dcOption($0) }
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
dict[-1460809483] = { return Api.Dialog.parse_dialog($0) } dict[-1460809483] = { return Api.Dialog.parse_dialog($0) }
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) } dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) }
@ -1255,6 +1256,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DcOption: case let _1 as Api.DcOption:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DefaultHistoryTTL:
_1.serialize(buffer, boxed)
case let _1 as Api.Dialog: case let _1 as Api.Dialog:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DialogFilter: case let _1 as Api.DialogFilter:

View File

@ -1852,15 +1852,16 @@ public extension Api.functions.channels {
} }
} }
public extension Api.functions.channels { public extension Api.functions.channels {
static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1029681423) buffer.appendInt32(-1862244601)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false) serializeString(about, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {geoPoint!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {geoPoint!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(address!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeString(address!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", String(describing: flags)), ("title", String(describing: title)), ("about", String(describing: about)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in if Int(flags) & Int(1 << 4) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", String(describing: flags)), ("title", String(describing: title)), ("about", String(describing: about)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address)), ("ttlPeriod", String(describing: ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -3725,16 +3726,18 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func createChat(users: [Api.InputUser], title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func createChat(flags: Int32, users: [Api.InputUser], title: String, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(164303470) buffer.appendInt32(3450904)
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count)) buffer.appendInt32(Int32(users.count))
for item in users { for item in users {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
serializeString(title, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.createChat", parameters: [("users", String(describing: users)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.createChat", parameters: [("flags", String(describing: flags)), ("users", String(describing: users)), ("title", String(describing: title)), ("ttlPeriod", String(describing: ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -4351,6 +4354,21 @@ public extension Api.functions.messages {
}) })
} }
} }
public extension Api.functions.messages {
static func getDefaultHistoryTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.DefaultHistoryTTL>) {
let buffer = Buffer()
buffer.appendInt32(1703637384)
return (FunctionDescription(name: "messages.getDefaultHistoryTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DefaultHistoryTTL? in
let reader = BufferReader(buffer)
var result: Api.DefaultHistoryTTL?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.DefaultHistoryTTL
}
return result
})
}
}
public extension Api.functions.messages { public extension Api.functions.messages {
static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.DhConfig>) { static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.DhConfig>) {
let buffer = Buffer() let buffer = Buffer()
@ -6335,6 +6353,21 @@ public extension Api.functions.messages {
}) })
} }
} }
public extension Api.functions.messages {
static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1632299963)
serializeInt32(period, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.setDefaultHistoryTTL", parameters: [("period", String(describing: period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.messages { public extension Api.functions.messages {
static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -836,6 +836,42 @@ public extension Api {
} }
} }
public extension Api {
enum DefaultHistoryTTL: TypeConstructorDescription {
case defaultHistoryTTL(period: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .defaultHistoryTTL(let period):
if boxed {
buffer.appendInt32(1135897376)
}
serializeInt32(period, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .defaultHistoryTTL(let period):
return ("defaultHistoryTTL", [("period", String(describing: period))])
}
}
public static func parse_defaultHistoryTTL(_ reader: BufferReader) -> DefaultHistoryTTL? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.DefaultHistoryTTL.defaultHistoryTTL(period: _1!)
}
else {
return nil
}
}
}
}
public extension Api { public extension Api {
enum Dialog: TypeConstructorDescription { enum Dialog: TypeConstructorDescription {
case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?) case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?)

View File

@ -97,8 +97,9 @@ public struct AccountPrivacySettings: Equatable {
public let automaticallyArchiveAndMuteNonContacts: Bool public let automaticallyArchiveAndMuteNonContacts: Bool
public let accountRemovalTimeout: Int32 public let accountRemovalTimeout: Int32
public let messageAutoremoveTimeout: Int32?
public init(presence: SelectivePrivacySettings, groupInvitations: SelectivePrivacySettings, voiceCalls: SelectivePrivacySettings, voiceCallsP2P: SelectivePrivacySettings, profilePhoto: SelectivePrivacySettings, forwards: SelectivePrivacySettings, phoneNumber: SelectivePrivacySettings, phoneDiscoveryEnabled: Bool, voiceMessages: SelectivePrivacySettings, automaticallyArchiveAndMuteNonContacts: Bool, accountRemovalTimeout: Int32) { public init(presence: SelectivePrivacySettings, groupInvitations: SelectivePrivacySettings, voiceCalls: SelectivePrivacySettings, voiceCallsP2P: SelectivePrivacySettings, profilePhoto: SelectivePrivacySettings, forwards: SelectivePrivacySettings, phoneNumber: SelectivePrivacySettings, phoneDiscoveryEnabled: Bool, voiceMessages: SelectivePrivacySettings, automaticallyArchiveAndMuteNonContacts: Bool, accountRemovalTimeout: Int32, messageAutoremoveTimeout: Int32?) {
self.presence = presence self.presence = presence
self.groupInvitations = groupInvitations self.groupInvitations = groupInvitations
self.voiceCalls = voiceCalls self.voiceCalls = voiceCalls
@ -110,6 +111,7 @@ public struct AccountPrivacySettings: Equatable {
self.voiceMessages = voiceMessages self.voiceMessages = voiceMessages
self.automaticallyArchiveAndMuteNonContacts = automaticallyArchiveAndMuteNonContacts self.automaticallyArchiveAndMuteNonContacts = automaticallyArchiveAndMuteNonContacts
self.accountRemovalTimeout = accountRemovalTimeout self.accountRemovalTimeout = accountRemovalTimeout
self.messageAutoremoveTimeout = messageAutoremoveTimeout
} }
public static func ==(lhs: AccountPrivacySettings, rhs: AccountPrivacySettings) -> Bool { public static func ==(lhs: AccountPrivacySettings, rhs: AccountPrivacySettings) -> Bool {
@ -146,6 +148,9 @@ public struct AccountPrivacySettings: Equatable {
if lhs.accountRemovalTimeout != rhs.accountRemovalTimeout { if lhs.accountRemovalTimeout != rhs.accountRemovalTimeout {
return false return false
} }
if lhs.messageAutoremoveTimeout != rhs.messageAutoremoveTimeout {
return false
}
return true return true
} }

View File

@ -34,7 +34,7 @@ private func createChannel(account: Account, title: String, description: String?
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers) transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers)
return account.network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address), automaticFloodWait: false) return account.network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: nil), automaticFloodWait: false)
|> mapError { error -> CreateChannelError in |> mapError { error -> CreateChannelError in
if error.errorCode == 406 { if error.errorCode == 406 {
return .serverProvided(error.errorDescription) return .serverProvided(error.errorDescription)

View File

@ -23,7 +23,7 @@ func _internal_createGroup(account: Account, title: String, peerIds: [PeerId]) -
return .single(nil) return .single(nil)
} }
} }
return account.network.request(Api.functions.messages.createChat(users: inputUsers, title: title)) return account.network.request(Api.functions.messages.createChat(flags: 0, users: inputUsers, title: title, ttlPeriod: nil))
|> mapError { error -> CreateGroupError in |> mapError { error -> CreateGroupError in
if error.errorDescription == "USERS_TOO_FEW" { if error.errorDescription == "USERS_TOO_FEW" {
return .privacy return .privacy

View File

@ -866,6 +866,19 @@ public extension TelegramEngine {
|> ignoreValues |> ignoreValues
} }
public func removeForumChannelThreads(id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
for threadId in threadIds {
cloudChatAddClearHistoryOperation(transaction: transaction, peerId: id, threadId: threadId, explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: CloudChatClearHistoryType(.forEveryone))
transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: nil)
_internal_clearHistory(transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: id, threadId: threadId, namespaces: .not(Namespaces.Message.allScheduled))
}
}
|> ignoreValues
}
public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> { public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
return self.account.postbox.transaction { transaction -> ([Int64], Int) in return self.account.postbox.transaction { transaction -> ([Int64], Int) in
var limit = 5 var limit = 5

View File

@ -32,6 +32,10 @@ public extension TelegramEngine {
public func updateAccountRemovalTimeout(timeout: Int32) -> Signal<Void, NoError> { public func updateAccountRemovalTimeout(timeout: Int32) -> Signal<Void, NoError> {
return _internal_updateAccountRemovalTimeout(account: self.account, timeout: timeout) return _internal_updateAccountRemovalTimeout(account: self.account, timeout: timeout)
} }
public func updateGlobalMessageRemovalTimeout(timeout: Int32?) -> Signal<Void, NoError> {
return _internal_updateMessageRemovalTimeout(account: self.account, timeout: timeout)
}
public func updatePhoneNumberDiscovery(value: Bool) -> Signal<Void, NoError> { public func updatePhoneNumberDiscovery(value: Bool) -> Signal<Void, NoError> {
return _internal_updatePhoneNumberDiscovery(account: self.account, value: value) return _internal_updatePhoneNumberDiscovery(account: self.account, value: value)

View File

@ -16,17 +16,29 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
let voiceMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyVoiceMessages)) let voiceMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyVoiceMessages))
let autoremoveTimeout = account.network.request(Api.functions.account.getAccountTTL()) let autoremoveTimeout = account.network.request(Api.functions.account.getAccountTTL())
let globalPrivacySettings = account.network.request(Api.functions.account.getGlobalPrivacySettings()) let globalPrivacySettings = account.network.request(Api.functions.account.getGlobalPrivacySettings())
return combineLatest(lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, autoremoveTimeout, globalPrivacySettings) let messageAutoremoveTimeout = account.network.request(Api.functions.messages.getDefaultHistoryTTL())
return combineLatest(lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, autoremoveTimeout, globalPrivacySettings, messageAutoremoveTimeout)
|> `catch` { _ in |> `catch` { _ in
return .complete() return .complete()
} }
|> mapToSignal { lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, autoremoveTimeout, globalPrivacySettings -> Signal<AccountPrivacySettings, NoError> in |> mapToSignal { lastSeenPrivacy, groupPrivacy, voiceCallPrivacy, voiceCallP2P, profilePhotoPrivacy, forwardPrivacy, phoneNumberPrivacy, phoneDiscoveryPrivacy, voiceMessagesPrivacy, autoremoveTimeout, globalPrivacySettings, messageAutoremoveTimeout -> Signal<AccountPrivacySettings, NoError> in
let accountTimeoutSeconds: Int32 let accountTimeoutSeconds: Int32
switch autoremoveTimeout { switch autoremoveTimeout {
case let .accountDaysTTL(days): case let .accountDaysTTL(days):
accountTimeoutSeconds = days * 24 * 60 * 60 accountTimeoutSeconds = days * 24 * 60 * 60
} }
let messageAutoremoveSeconds: Int32?
switch messageAutoremoveTimeout {
case let .defaultHistoryTTL(period):
if period != 0 {
messageAutoremoveSeconds = period
} else {
messageAutoremoveSeconds = nil
}
}
let lastSeenRules: [Api.PrivacyRule] let lastSeenRules: [Api.PrivacyRule]
let groupRules: [Api.PrivacyRule] let groupRules: [Api.PrivacyRule]
let voiceRules: [Api.PrivacyRule] let voiceRules: [Api.PrivacyRule]
@ -143,7 +155,7 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
return updated return updated
}) })
return AccountPrivacySettings(presence: SelectivePrivacySettings(apiRules: lastSeenRules, peers: peerMap), groupInvitations: SelectivePrivacySettings(apiRules: groupRules, peers: peerMap), voiceCalls: SelectivePrivacySettings(apiRules: voiceRules, peers: peerMap), voiceCallsP2P: SelectivePrivacySettings(apiRules: voiceP2PRules, peers: peerMap), profilePhoto: SelectivePrivacySettings(apiRules: profilePhotoRules, peers: peerMap), forwards: SelectivePrivacySettings(apiRules: forwardRules, peers: peerMap), phoneNumber: SelectivePrivacySettings(apiRules: phoneNumberRules, peers: peerMap), phoneDiscoveryEnabled: phoneDiscoveryValue, voiceMessages: SelectivePrivacySettings(apiRules: voiceMessagesRules, peers: peerMap), automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: accountTimeoutSeconds) return AccountPrivacySettings(presence: SelectivePrivacySettings(apiRules: lastSeenRules, peers: peerMap), groupInvitations: SelectivePrivacySettings(apiRules: groupRules, peers: peerMap), voiceCalls: SelectivePrivacySettings(apiRules: voiceRules, peers: peerMap), voiceCallsP2P: SelectivePrivacySettings(apiRules: voiceP2PRules, peers: peerMap), profilePhoto: SelectivePrivacySettings(apiRules: profilePhotoRules, peers: peerMap), forwards: SelectivePrivacySettings(apiRules: forwardRules, peers: peerMap), phoneNumber: SelectivePrivacySettings(apiRules: phoneNumberRules, peers: peerMap), phoneDiscoveryEnabled: phoneDiscoveryValue, voiceMessages: SelectivePrivacySettings(apiRules: voiceMessagesRules, peers: peerMap), automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: accountTimeoutSeconds, messageAutoremoveTimeout: messageAutoremoveSeconds)
} }
} }
} }
@ -164,6 +176,16 @@ func _internal_updateAccountRemovalTimeout(account: Account, timeout: Int32) ->
} }
} }
func _internal_updateMessageRemovalTimeout(account: Account, timeout: Int32?) -> Signal<Void, NoError> {
return account.network.request(Api.functions.messages.setDefaultHistoryTTL(period: timeout ?? 0))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
func _internal_updatePhoneNumberDiscovery(account: Account, value: Bool) -> Signal<Void, NoError> { func _internal_updatePhoneNumberDiscovery(account: Account, value: Bool) -> Signal<Void, NoError> {
var rules: [Api.InputPrivacyRule] = [] var rules: [Api.InputPrivacyRule] = []
if value { if value {

View File

@ -916,12 +916,14 @@ public final class NavigationButtonComponent: Component {
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
self.moreButton?.play()
component.pressed(self) component.pressed(self)
} }
moreButton.contextAction = { [weak self] sourceNode, gesture in moreButton.contextAction = { [weak self] sourceNode, gesture in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
self.moreButton?.play()
component.contextAction?(self, gesture) component.contextAction?(self, gesture)
} }
self.moreButton = moreButton self.moreButton = moreButton

View File

@ -30,7 +30,6 @@ public final class ChatTimerScreen: ViewController {
private var animatedIn = false private var animatedIn = false
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId
private let style: ChatTimerScreenStyle private let style: ChatTimerScreenStyle
private let mode: ChatTimerScreenMode private let mode: ChatTimerScreenMode
private let currentTime: Int32? private let currentTime: Int32?
@ -40,9 +39,8 @@ public final class ChatTimerScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode = .sendTimer, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode = .sendTimer, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
self.context = context self.context = context
self.peerId = peerId
self.style = style self.style = style
self.mode = mode self.mode = mode
self.currentTime = currentTime self.currentTime = currentTime

View File

@ -14934,7 +14934,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if let navigationController = self.effectiveNavigationController { if let navigationController = self.effectiveNavigationController {
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer) var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
if let message = message, let threadId = message.threadId { if case let .channel(channel) = peer, channel.flags.contains(.isForum), let message = message, let threadId = message.threadId {
chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
} }
@ -17171,10 +17171,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
private func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { private func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
guard case let .peer(peerId) = self.chatLocation else { guard case .peer = self.chatLocation else {
return return
} }
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peerId, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in
completion(time) completion(time)
}) })
self.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
@ -17231,7 +17231,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -2367,8 +2367,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound) return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound)
}) })
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last { if let loaded = displayedRange.visibleRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
if !"".isEmpty {
print("load next")
return
}
let locationInput: ChatHistoryLocation = .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount, highlight: false) let locationInput: ChatHistoryLocation = .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount, highlight: false)
if self.chatHistoryLocationValue?.content != locationInput { if self.chatHistoryLocationValue?.content != locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
@ -2378,6 +2382,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId()) self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId())
} }
} else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil {
if !"".isEmpty {
print("load previous")
return
}
let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false) let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false)
if self.chatHistoryLocationValue?.content != locationInput { if self.chatHistoryLocationValue?.content != locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())

View File

@ -17,12 +17,28 @@ import ForumCreateTopicScreen
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {
if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) { if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
for controller in params.navigationController.viewControllers.reversed() { for controller in params.navigationController.viewControllers.reversed() {
if let controller = controller as? ChatListControllerImpl, case let .forum(peerId) = controller.location, peer.id == peerId { var chatListController: ChatListControllerImpl?
let _ = params.navigationController.popToViewController(controller, animated: params.animated) if let controller = controller as? ChatListControllerImpl {
if let activateMessageSearch = params.activateMessageSearch { chatListController = controller
controller.activateSearch(query: activateMessageSearch.1) } else if let controller = controller as? TabBarController {
chatListController = controller.currentController as? ChatListControllerImpl
}
if let chatListController = chatListController {
var matches = false
if case let .forum(peerId) = chatListController.location, peer.id == peerId {
matches = true
} else if case let .forum(peerId) = chatListController.effectiveLocation, peer.id == peerId {
matches = true
}
if matches {
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
if let activateMessageSearch = params.activateMessageSearch {
chatListController.activateSearch(query: activateMessageSearch.1)
}
return
} }
return
} }
} }

View File

@ -28,6 +28,7 @@ import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import ComponentDisplayAdapters import ComponentDisplayAdapters
import ChatTitleView import ChatTitleView
import AppBundle
enum PeerInfoHeaderButtonKey: Hashable { enum PeerInfoHeaderButtonKey: Hashable {
case message case message
@ -2083,6 +2084,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let subtitleNodeContainer: ASDisplayNode let subtitleNodeContainer: ASDisplayNode
let subtitleNodeRawContainer: ASDisplayNode let subtitleNodeRawContainer: ASDisplayNode
let subtitleNode: MultiScaleTextNode let subtitleNode: MultiScaleTextNode
var subtitleBackgroundNode: ASDisplayNode?
var subtitleBackgroundButton: HighlightTrackingButtonNode?
var subtitleArrowNode: ASImageNode?
let panelSubtitleNode: MultiScaleTextNode let panelSubtitleNode: MultiScaleTextNode
let nextPanelSubtitleNode: MultiScaleTextNode let nextPanelSubtitleNode: MultiScaleTextNode
let usernameNodeContainer: ASDisplayNode let usernameNodeContainer: ASDisplayNode
@ -2111,6 +2115,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)? var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)?
var navigateToForum: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition? var navigationTransition: PeerInfoHeaderNavigationTransition?
var backgroundAlpha: CGFloat = 1.0 var backgroundAlpha: CGFloat = 1.0
@ -2161,7 +2167,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.usernameNode.displaysAsynchronously = false self.usernameNode.displaysAsynchronously = false
self.buttonsContainerNode = SparseNode() self.buttonsContainerNode = SparseNode()
self.buttonsContainerNode.clipsToBounds = true self.buttonsContainerNode.clipsToBounds = false
self.regularContentNode = PeerInfoHeaderRegularContentNode() self.regularContentNode = PeerInfoHeaderRegularContentNode()
var requestUpdateLayoutImpl: (() -> Void)? var requestUpdateLayoutImpl: (() -> Void)?
@ -2300,6 +2306,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
} }
@objc private func subtitleBackgroundPressed() {
self.navigateToForum?()
}
func invokeDisplayPremiumIntro() { func invokeDisplayPremiumIntro() {
self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded) self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded)
} }
@ -2616,6 +2626,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let titleString: NSAttributedString let titleString: NSAttributedString
let smallSubtitleString: NSAttributedString let smallSubtitleString: NSAttributedString
let subtitleString: NSAttributedString let subtitleString: NSAttributedString
var subtitleIsButton: Bool = false
var panelSubtitleString: NSAttributedString? var panelSubtitleString: NSAttributedString?
var nextPanelSubtitleString: NSAttributedString? var nextPanelSubtitleString: NSAttributedString?
let usernameString: NSAttributedString let usernameString: NSAttributedString
@ -2659,18 +2670,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
} else if let _ = threadData { } else if let _ = threadData {
let subtitleColor: UIColor = presentationData.theme.list.itemSecondaryTextColor let subtitleColor: UIColor
subtitleColor = presentationData.theme.list.itemAccentColor
let statusText: String let statusText: String
if let addressName = peer.addressName { statusText = peer.debugDisplayTitle
statusText = presentationData.strings.PeerInfo_TopicHeaderLocation("@\(addressName)").string
} else {
statusText = presentationData.strings.PeerInfo_TopicHeaderLocation(peer.debugDisplayTitle).string
}
smallSubtitleString = NSAttributedString(string: statusText, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) smallSubtitleString = NSAttributedString(string: statusText, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7))
subtitleString = NSAttributedString(string: statusText, font: Font.regular(17.0), textColor: subtitleColor) subtitleString = NSAttributedString(string: statusText, font: Font.semibold(15.0), textColor: subtitleColor)
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
subtitleIsButton = true
let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData
if let panelStatusData = maybePanelStatusData { if let panelStatusData = maybePanelStatusData {
@ -2739,6 +2749,81 @@ final class PeerInfoHeaderNode: ASDisplayNode {
], mainState: TitleNodeStateRegular) ], mainState: TitleNodeStateRegular)
self.subtitleNode.accessibilityLabel = subtitleString.string self.subtitleNode.accessibilityLabel = subtitleString.string
if subtitleIsButton {
let subtitleBackgroundNode: ASDisplayNode
if let current = self.subtitleBackgroundNode {
subtitleBackgroundNode = current
} else {
subtitleBackgroundNode = ASDisplayNode()
self.subtitleBackgroundNode = subtitleBackgroundNode
self.subtitleNode.insertSubnode(subtitleBackgroundNode, at: 0)
}
let subtitleBackgroundButton: HighlightTrackingButtonNode
if let current = self.subtitleBackgroundButton {
subtitleBackgroundButton = current
} else {
subtitleBackgroundButton = HighlightTrackingButtonNode()
self.subtitleBackgroundButton = subtitleBackgroundButton
self.subtitleNode.addSubnode(subtitleBackgroundButton)
subtitleBackgroundButton.addTarget(self, action: #selector(self.subtitleBackgroundPressed), forControlEvents: .touchUpInside)
subtitleBackgroundButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.subtitleNode.layer.removeAnimation(forKey: "opacity")
self.subtitleNode.alpha = 0.4
} else {
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
let subtitleArrowNode: ASImageNode
if let current = self.subtitleArrowNode {
subtitleArrowNode = current
if themeUpdated {
subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5))
}
} else {
subtitleArrowNode = ASImageNode()
self.subtitleArrowNode = subtitleArrowNode
self.subtitleNode.insertSubnode(subtitleArrowNode, at: 1)
subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5))
}
subtitleBackgroundNode.backgroundColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.1)
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -6.0, dy: -4.0)
subtitleBackgroundFrame.size.width += 12.0
transition.updateFrame(node: subtitleBackgroundNode, frame: subtitleBackgroundFrame)
transition.updateCornerRadius(node: subtitleBackgroundNode, cornerRadius: subtitleBackgroundFrame.height * 0.5)
transition.updateFrame(node: subtitleBackgroundButton, frame: subtitleBackgroundFrame)
if let arrowImage = subtitleArrowNode.image {
let scaleFactor: CGFloat = 0.8
let arrowSize = CGSize(width: floorToScreenPixels(arrowImage.size.width * scaleFactor), height: floorToScreenPixels(arrowImage.size.height * scaleFactor))
subtitleArrowNode.frame = CGRect(origin: CGPoint(x: subtitleBackgroundFrame.maxX - arrowSize.width - 1.0, y: subtitleBackgroundFrame.minY + floor((subtitleBackgroundFrame.height - arrowSize.height) / 2.0)), size: arrowSize)
}
} else {
if let subtitleBackgroundNode = self.subtitleBackgroundNode {
self.subtitleBackgroundNode = nil
subtitleBackgroundNode.removeFromSupernode()
}
if let subtitleArrowNode = self.subtitleArrowNode {
self.subtitleArrowNode = nil
subtitleArrowNode.removeFromSupernode()
}
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
self.subtitleBackgroundButton = nil
subtitleBackgroundButton.removeFromSupernode()
}
}
if let previousPanelStatusData = previousPanelStatusData, let currentPanelStatusData = panelStatusData.0, let previousPanelStatusDataKey = previousPanelStatusData.key, let currentPanelStatusDataKey = currentPanelStatusData.key, previousPanelStatusDataKey != currentPanelStatusDataKey { if let previousPanelStatusData = previousPanelStatusData, let currentPanelStatusData = panelStatusData.0, let previousPanelStatusDataKey = previousPanelStatusData.key, let currentPanelStatusDataKey = currentPanelStatusData.key, previousPanelStatusDataKey != currentPanelStatusDataKey {
if let snapshotView = self.panelSubtitleNode.view.snapshotContentTree() { if let snapshotView = self.panelSubtitleNode.view.snapshotContentTree() {
let direction: CGFloat = previousPanelStatusDataKey.rawValue > currentPanelStatusDataKey.rawValue ? 1.0 : -1.0 let direction: CGFloat = previousPanelStatusDataKey.rawValue > currentPanelStatusDataKey.rawValue ? 1.0 : -1.0
@ -2803,7 +2888,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
var titleFrame: CGRect var titleFrame: CGRect
let subtitleFrame: CGRect var subtitleFrame: CGRect
let usernameFrame: CGRect let usernameFrame: CGRect
let usernameSpacing: CGFloat = 4.0 let usernameSpacing: CGFloat = 4.0
@ -2829,6 +2914,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
} }
if subtitleIsButton {
subtitleFrame.origin.y += 11.0
}
let singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0 let singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0
let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset
@ -3025,7 +3114,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.avatarContainerNode.canAttachVideo = false self.avatarListNode.avatarContainerNode.canAttachVideo = false
} }
let panelWithAvatarHeight: CGFloat = 35.0 + avatarSize var panelWithAvatarHeight: CGFloat = 35.0 + avatarSize
if threadData != nil {
panelWithAvatarHeight += 10.0
}
let rawHeight: CGFloat let rawHeight: CGFloat
let height: CGFloat let height: CGFloat
@ -3307,6 +3399,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
} }
if let subtitleBackgroundButton = self.subtitleBackgroundButton, subtitleBackgroundButton.view.convert(subtitleBackgroundButton.bounds, to: self.view).contains(point) {
if let result = subtitleBackgroundButton.view.hitTest(self.view.convert(point, to: subtitleBackgroundButton.view), with: event) {
return result
}
}
if result.isDescendant(of: self.navigationButtonContainer.view) { if result.isDescendant(of: self.navigationButtonContainer.view) {
return result return result
} }
@ -3314,6 +3412,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view { if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view {
return nil return nil
} }
return result return result
} }

View File

@ -3445,6 +3445,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
strongSelf.controller?.presentInGlobalOverlay(contextController) strongSelf.controller?.presentInGlobalOverlay(contextController)
} }
self.headerNode.navigateToForum = { [weak self] in
guard let self, let navigationController = self.controller?.navigationController as? NavigationController, let peer = self.data?.peer else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer))))
}
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) { if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) {
self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId)) self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId))
} }
@ -4987,7 +4994,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
private func openAutoremove(currentValue: Int32?) { private func openAutoremove(currentValue: Int32?) {
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -5016,7 +5023,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
private func openCustomMute() { private func openCustomMute() {
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -591,12 +591,21 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
} }
case let .channelMessage(id, timecode): case let .channelMessage(id, timecode):
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(id)) let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id)
|> map { info -> ResolvedUrl? in return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false))
if let _ = info { |> take(1)
return .replyThread(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id)) |> mapToSignal { messages -> Signal<ResolvedUrl?, NoError> in
if let threadId = messages.first?.threadId {
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId)
|> map { info -> ResolvedUrl? in
if let _ = info {
return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)
} else {
return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
} else { } else {
return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)))
} }
} }
} else { } else {