chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
+8 -1
View File
@@ -29,7 +29,6 @@ swift_library(
"//submodules/ActivityIndicator:ActivityIndicator",
"//submodules/SearchBarNode:SearchBarNode",
"//submodules/ChatListSearchRecentPeersNode:ChatListSearchRecentPeersNode",
"//submodules/ChatListSearchItemNode:ChatListSearchItemNode",
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
"//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager",
"//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager",
@@ -118,6 +117,14 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode",
"//submodules/TelegramUI/Components/HeaderPanelContainerComponent",
"//submodules/TelegramUI/Components/HorizontalTabsComponent",
"//submodules/TelegramUI/Components/GlobalControlPanelsContext",
"//submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent",
"//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent",
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
"//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent",
],
visibility = [
"//visibility:public",
@@ -169,7 +169,7 @@ public class ChatListAdditionalCategoryItemNode: ItemListRevealOptionsItemNode {
self.titleNode = TextNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.isAccessibilityElement = true
@@ -450,10 +450,10 @@ final class ChatListContainerItemNode: ASDisplayNode {
self.layoutAdditionalPanels(transition: transition)
let edgeEffectHeight: CGFloat = insets.bottom
let edgeEffectHeight: CGFloat = insets.bottom + 8.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition))
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectFrame.height, 40.0), transition: ComponentTransition(transition))
transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0)
}
File diff suppressed because it is too large Load Diff
@@ -21,7 +21,13 @@ import ChatFolderLinkPreviewScreen
import ChatListHeaderComponent
import StoryPeerListComponent
import TelegramNotices
import EdgeEffect
import HeaderPanelContainerComponent
import HorizontalTabsComponent
import PremiumUI
import MediaPlaybackHeaderPanelComponent
import LiveLocationHeaderPanelComponent
import ChatListHeaderNoticeComponent
import ChatListFilterTabContainerNode
public enum ChatListContainerNodeFilter: Equatable {
case all
@@ -132,6 +138,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
}
public var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition, Bool) -> Void)?
public private(set) var isSwitchingCurrentItemFilterByDragging: Bool = false
public var currentItemFilter: ChatListFilterTabEntryId {
return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all
}
@@ -575,6 +582,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
itemNode.layer.removeAllAnimations()
}
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate)
self.isSwitchingCurrentItemFilterByDragging = true
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true)
}
}
@@ -651,6 +659,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
}
}
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate)
self.isSwitchingCurrentItemFilterByDragging = true
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
}
case .cancelled, .ended:
@@ -712,6 +721,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] {
self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode)
}
self.isSwitchingCurrentItemFilterByDragging = false
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
}
default:
@@ -1091,9 +1101,10 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
private var toolbarNode: ToolbarNode?
var toolbarActionSelected: ((ToolbarActionOption) -> Void)?
private var isSearchDisplayControllerActive: Bool = false
private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch?
private var skipSearchDisplayControllerLayout: Bool = false
private(set) var searchDisplayController: SearchDisplayController?
private var disappearingSearchDisplayController: SearchDisplayController?
var isReorderingFilters: Bool = false
var didBeginSelectingChatsWhileEditing: Bool = false
@@ -1353,14 +1364,226 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: ComponentTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) {
let headerContent = self.controller?.updateHeaderContent()
var tabsNode: ASDisplayNode?
var tabsNodeIsSearch = false
var panels: [HeaderPanelContainerComponent.Panel] = []
if let chatListNotice = self.controller?.globalControlPanelsContextState?.chatListNotice {
panels.append(HeaderPanelContainerComponent.Panel(
key: "chatListNotice",
orderIndex: 0,
component: AnyComponent(ChatListHeaderNoticeComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: chatListNotice,
activateAction: { [weak self] notice in
guard let self else {
return
}
switch notice {
case .clearStorage:
self.effectiveContainerNode.currentItemNode.interaction?.openStorageManagement()
case .setupPassword:
self.effectiveContainerNode.currentItemNode.interaction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumIntro()
case .xmasPremiumGift:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift([], nil)
case .premiumGrace:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumManagement()
case .setupBirthday:
self.effectiveContainerNode.currentItemNode.interaction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
self.effectiveContainerNode.currentItemNode.interaction?.openStarsTopup(amount.value)
case .setupPhoto:
self.effectiveContainerNode.currentItemNode.interaction?.openPhotoSetup()
case .accountFreeze:
self.effectiveContainerNode.currentItemNode.interaction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
self.effectiveContainerNode.currentItemNode.interaction?.openUrl(url)
}
},
dismissAction: { [weak self] notice in
guard let self, let controller = self.controller else {
return
}
controller.globalControlPanelsContext.dismissChatListNotice(parentController: controller, notice: notice)
},
selectAction: { [weak self] notice, isPositive in
guard let self else {
return
}
switch notice {
case let .reviewLogin(newSessionReview, _):
self.effectiveContainerNode.currentItemNode.interaction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
)))
)
}
if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
panels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
}
if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation {
panels.append(HeaderPanelContainerComponent.Panel(
key: "liveLocation",
orderIndex: 2,
component: AnyComponent(LiveLocationHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: liveLocation,
controller: { [weak self] in
return self?.controller
}
)))
)
}
if let value = self.controller?.searchTabsNode {
tabsNode = value
tabsNodeIsSearch = true
} else if let value = self.controller?.tabsNode, self.controller?.hasTabs == true {
tabsNode = value
var navigationHeaderPanels: AnyComponent<Empty>?
if self.controller?.tabContainerData != nil || !panels.isEmpty {
var tabs: AnyComponent<Empty>?
if let tabContainerData = self.controller?.tabContainerData, tabContainerData.0.count > 1 {
let selectedTab: HorizontalTabsComponent.Tab.Id
switch self.effectiveContainerNode.currentItemFilter {
case .all:
selectedTab = AnyHashable(Int32.min)
case let .filter(id):
selectedTab = AnyHashable(id)
}
let isEditing = self.isReorderingFilters || (self.mainContainerNode.currentItemNode.currentState.editing && !self.didBeginSelectingChatsWhileEditing)
tabs = AnyComponent(HorizontalTabsComponent(
context: self.context,
theme: self.presentationData.theme,
tabs: tabContainerData.0.map { entry -> HorizontalTabsComponent.Tab in
let id: HorizontalTabsComponent.Tab.Id
let title: HorizontalTabsComponent.Tab.Title
var badge: HorizontalTabsComponent.Tab.Badge?
var isMainTab = false
switch entry {
case .all:
id = Int32.min
title = HorizontalTabsComponent.Tab.Title(text: self.presentationData.strings.ChatList_Tabs_All, entities: [], enableAnimations: false)
isMainTab = true
case let .filter(idValue, text, unread):
id = AnyHashable(idValue)
title = HorizontalTabsComponent.Tab.Title(text: text.text, entities: text.entities, enableAnimations: text.enableAnimations)
if unread.value != 0 {
badge = HorizontalTabsComponent.Tab.Badge(
title: "\(unread.value)",
isAccent: unread.hasUnmuted
)
}
}
return HorizontalTabsComponent.Tab(
id: id,
content: .title(title),
badge: badge,
action: { [weak self] in
guard let self, let tabContainerData = self.controller?.tabContainerData else {
return
}
let isPremium = self.context.isPremium
let mappedId: ChatListFilterTabEntryId = entry.id
var isDisabled = false
if let filtersLimit = tabContainerData.2 {
guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == mappedId }) else {
return
}
isDisabled = !isPremium && folderIndex >= filtersLimit
}
if isDisabled {
let filtersCount = tabContainerData.0.count(where: { item in
if case .all = item {
return false
} else {
return true
}
})
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filtersCount), action: {
let controller = PremiumIntroScreen(context: context, source: .folders)
replaceImpl?(controller)
return true
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
self.controller?.push(controller)
} else {
self.controller?.selectTab(id: mappedId)
}
},
contextAction: { [weak self] sourceView, gesture in
guard let self, let tabContainerData = self.controller?.tabContainerData else {
return
}
let isPremium = self.context.isPremium
let mappedId: Int32?
switch entry {
case .all:
mappedId = nil
case let .filter(idValue, _, _):
mappedId = idValue
}
var isDisabled = false
if let filtersLimit = tabContainerData.2 {
guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == entry.id }) else {
return
}
isDisabled = !isPremium && folderIndex >= filtersLimit
}
self.controller?.tabContextGesture(id: mappedId, sourceNode: nil, sourceView: sourceView, gesture: gesture, keepInPlace: false, isDisabled: isDisabled)
},
deleteAction: (!isEditing || isMainTab) ? nil : { [weak self] in
guard let self else {
return
}
if case let .filter(id) = entry.id {
self.controller?.askForFilterRemoval(id: id)
}
}
)
},
selectedTab: selectedTab,
isEditing: isEditing,
liftWhileSwitching: layout.deviceMetrics.type != .tablet
))
}
navigationHeaderPanels = AnyComponent(HeaderPanelContainerComponent(
theme: self.presentationData.theme,
tabs: tabs,
panels: panels
))
}
var effectiveStorySubscriptions: EngineStorySubscriptions?
@@ -1382,16 +1605,17 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
strings: self.presentationData.strings,
statusBarHeight: layout.statusBarHeight ?? 0.0,
sideInset: layout.safeInsets.left,
isSearchActive: self.isSearchDisplayControllerActive,
isSearchEnabled: true,
search: ChatListNavigationBar.Search(isEnabled: true),
activeSearch: self.isSearchDisplayControllerActive,
primaryContent: headerContent?.primaryContent,
secondaryContent: headerContent?.secondaryContent,
secondaryTransition: self.inlineStackContainerTransitionFraction,
storySubscriptions: effectiveStorySubscriptions,
storiesIncludeHidden: self.location == .chatList(groupId: .archive),
uploadProgress: self.controller?.storyUploadProgress ?? [:],
tabsNode: tabsNode,
tabsNodeIsSearch: tabsNodeIsSearch,
headerPanels: navigationHeaderPanels,
tabsNode: nil,
tabsNodeIsSearch: false,
accessoryPanelContainer: self.controller?.accessoryPanelContainer,
accessoryPanelContainerHeight: self.controller?.accessoryPanelContainerHeight ?? 0.0,
activateSearch: { [weak self] searchContentNode in
@@ -1479,7 +1703,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
}
var offset = resultingOffset
if self.isSearchDisplayControllerActive {
if self.isSearchDisplayControllerActive != nil {
offset = 0.0
}
@@ -1656,6 +1880,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition)
}
}
if let disappearingSearchDisplayController = self.disappearingSearchDisplayController {
disappearingSearchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition)
}
self.updateNavigationScrolling(navigationHeight: navigationBarLayout.navigationHeight, transition: transition)
@@ -1666,7 +1893,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
}
@MainActor
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) async -> (ASDisplayNode, (Bool) -> Void)? {
func activateSearch(placeholderNode: SearchBarPlaceholderNode?, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?, searchBarIsExternal: Bool) async -> ((Bool) -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight, _) = self.containerLayout, self.searchDisplayController == nil else {
return nil
}
@@ -1712,16 +1939,16 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()
}
})
}, fieldStyle: placeholderNode?.fieldStyle ?? .modern, searchBarIsExternal: searchBarIsExternal)
self.mainContainerNode.accessibilityElementsHidden = true
self.inlineStackContainerNode?.accessibilityElementsHidden = true
return (contentNode.filterContainerNode, { [weak self] focus in
return ({ [weak self] focus in
guard let strongSelf = self else {
return
}
strongSelf.isSearchDisplayControllerActive = true
strongSelf.isSearchDisplayControllerActive = ChatListNavigationBar.ActiveSearch(isExternal: placeholderNode == nil)
strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in
@@ -1731,7 +1958,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if isSearchBar {
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
navigationBarComponentView.addSubnode(subnode)
navigationBarComponentView.searchContentNode?.addSubnode(subnode)
}
} else {
self.insertSubnode(subnode, aboveSubnode: self.debugListView)
@@ -1742,21 +1969,31 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
})
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? {
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode?, animated: Bool) -> (() -> Void)? {
if let searchDisplayController = self.searchDisplayController {
self.isSearchDisplayControllerActive = false
self.isSearchDisplayControllerActive = nil
self.searchDisplayController = nil
self.disappearingSearchDisplayController = searchDisplayController
self.mainContainerNode.accessibilityElementsHidden = false
self.inlineStackContainerNode?.accessibilityElementsHidden = false
return { [weak self, weak placeholderNode] in
if let strongSelf = self, let placeholderNode, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
guard let self, let (layout, _, _, cleanNavigationBarHeight, _) = self.containerLayout else {
return
}
let placeholderNode = placeholderNode
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated, completion: { [weak self, weak searchDisplayController] in
guard let self, let searchDisplayController else {
return
}
if self.disappearingSearchDisplayController === searchDisplayController {
self.disappearingSearchDisplayController = nil
}
})
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
} else {
return nil
@@ -148,7 +148,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.isAccessibilityElement = true
@@ -1283,7 +1283,6 @@ private final class ChatListFilterPresetController: ItemListController {
pendingUnpinnedAllMessages: false,
activeGroupCallInfo: nil,
hasActiveGroupCall: false,
importState: nil,
threadData: nil,
isGeneralThreadClosed: nil,
replyMessage: nil,
@@ -1753,14 +1752,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
if initialPreset == nil {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let text = presentationData.strings.ChatListFilter_AlertCreateFolderBeforeSharingText
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let state = stateValue.with({ $0 })
if state.additionallyIncludePeers.isEmpty {
let text = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return
}
@@ -1785,7 +1784,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
statusController?.dismiss()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
presentControllerImpl?(textAlertController(context: context, title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return
}
@@ -2360,7 +2359,7 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
case .someUserTooManyChannels:
text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit
}
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
})
}
})
@@ -5,6 +5,7 @@ import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import PresentationDataUtils
import ItemListUI
import AccountContext
import ItemListPeerActionItem
@@ -516,13 +517,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
}
if hasLinks {
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
confirmDeleteFolder()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})
]))
], actionLayout: .vertical))
} else {
confirmDeleteFolder()
}
@@ -201,7 +201,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.titleNode.textNode)
@@ -132,7 +132,7 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
@@ -113,7 +113,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = [.staticText, .header]
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.accessoryTextNode)
@@ -79,7 +79,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
@@ -36,6 +36,10 @@ import MultiAnimationRenderer
import PremiumUI
import AvatarNode
import StoryContainerScreen
import ChatListSearchFiltersContainerNode
import EdgeEffect
import ComponentFlow
import ComponentDisplayAdapters
private enum ChatListTokenId: Int32 {
case archive
@@ -107,8 +111,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var dismissSearch: (() -> Void)?
var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)?
private let dimNode: ASDisplayNode
let filterContainerNode: ChatListSearchFiltersContainerNode
private let edgeEffectView: EdgeEffectView
private let filterContainerNode: ChatListSearchFiltersContainerNode
private let paneContainerNode: ChatListSearchPaneContainerNode
private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode?
@@ -181,9 +186,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.openMessage = originalOpenMessage
self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.edgeEffectView = EdgeEffectView()
self.filterContainerNode = ChatListSearchFiltersContainerNode()
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, requestPeerType: self.requestPeerType, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController, parentController: parentController())
@@ -193,7 +197,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor
// self.addSubnode(self.dimNode)
self.addSubnode(self.paneContainerNode)
let interaction = ChatListSearchInteraction(openPeer: { peer, chatPeer, threadId, value in
@@ -325,6 +328,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
parentController()?.view.endEditing(true)
}
self.view.addSubview(self.edgeEffectView)
self.addSubnode(self.filterContainerNode)
self.filterContainerNode.filterPressed = { [weak self] filter in
guard let strongSelf = self else {
return
@@ -553,9 +559,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
public override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
}
public override var hasDim: Bool {
@@ -705,18 +708,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.transitionFraction = transitionFraction
if let (layout, _) = self.validLayout {
let filters: [ChatListSearchFilter]
if let suggestedFilters = self.suggestedFilters, !suggestedFilters.isEmpty {
filters = suggestedFilters
} else {
var isForum = false
if case .forum = self.location {
isForum = true
}
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: !isForum && self.hasDownloads, hasPublicPosts: self.showPublicPostsTab).map(\.filter)
}
self.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: transition)
self.updateFilterContainerNode(layout: layout, transition: transition)
}
}
@@ -762,18 +754,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.cancel?()
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
let isFirstTime = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight)
let topInset = navigationBarHeight
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0)))
private func updateFilterContainerNode(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var isForum = false
if case .forum = self.location {
isForum = true
@@ -786,8 +768,46 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads, hasPublicPosts: self.showPublicPostsTab).map(\.filter)
}
let overflowInset: CGFloat = 20.0
self.filterContainerNode.update(size: CGSize(width: layout.size.width - overflowInset * 2.0, height: 38.0), sideInset: layout.safeInsets.left - overflowInset, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
var filtersInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: layout.insets(options: [.input]).bottom + 34.0, right: 12.0)
if layout.insets(options: [.input]).bottom <= 30.0 {
filtersInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.insets(options: [.input]).bottom, innerDiameter: 40.0, sideInset: 32.0)
} else if layout.insets(options: [.input]).bottom <= 84.0 {
filtersInsets.left = 20.0
filtersInsets.right = filtersInsets.left
}
self.filterContainerNode.update(size: CGSize(width: layout.size.width - (layout.safeInsets.left + filtersInsets.left) * 2.0, height: 40.0), sideInset: 0.0, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
let isFirstTime = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight)
let topInset = navigationBarHeight
var filtersInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: layout.insets(options: [.input]).bottom, right: 12.0)
if filtersInsets.bottom == 84.0 {
filtersInsets.bottom -= 6.0
}
if layout.insets(options: [.input]).bottom <= 30.0 {
filtersInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.insets(options: [.input]).bottom, innerDiameter: 40.0, sideInset: 32.0)
} else if layout.insets(options: [.input]).bottom <= 84.0 {
filtersInsets.left = 20.0
filtersInsets.right = filtersInsets.left
} else {
if let inputHeight = layout.inputHeight, filtersInsets.bottom == inputHeight {
filtersInsets.bottom += 8.0
}
filtersInsets.bottom = max(8.0, filtersInsets.bottom)
}
if self.stateValue.selectedMessageIds != nil {
filtersInsets.bottom += 48.0
}
transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + filtersInsets.left, y: layout.size.height - filtersInsets.bottom - 40.0), size: CGSize(width: layout.size.width - (layout.safeInsets.left + filtersInsets.left) * 2.0, height: 40.0)))
self.updateFilterContainerNode(layout: layout, transition: transition)
if isFirstTime {
self.filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
@@ -795,13 +815,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
var bottomIntrinsicInset = layout.intrinsicInsets.bottom
if case .chatList(.root) = self.location {
if layout.safeInsets.left > overflowInset {
/*if case .chatList(.root) = self.location {
if layout.safeInsets.left > 20.0 {
bottomIntrinsicInset -= 34.0
} else {
bottomIntrinsicInset -= 49.0
}
}
}*/
if let selectedMessageIds = self.stateValue.selectedMessageIds {
var wasAdded = false
@@ -927,7 +947,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
}
self.selectionPanelNode = selectionPanelNode
self.addSubnode(selectionPanelNode)
self.insertSubnode(selectionPanelNode, aboveSubnode: self.filterContainerNode)
}
selectionPanelNode.selectedMessages = selectedMessageIds
@@ -948,25 +968,36 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
}
transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset, width: layout.size.width, height: layout.size.height - topInset))
transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height))
var bottomInset = layout.intrinsicInsets.bottom
if let inputHeight = layout.inputHeight {
bottomInset = inputHeight
} else if let _ = self.selectionPanelNode {
bottomInset = bottomIntrinsicInset
} else if case .chatList(.root) = self.location {
bottomInset -= bottomIntrinsicInset
}
bottomInset += 10.0
let availablePanes: [ChatListSearchPaneKey]
var isForum = false
if case .forum = self.location {
isForum = true
}
if self.displaySearchFilters {
availablePanes = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads, hasPublicPosts: self.hasPublicPostsTab)
} else {
availablePanes = isForum ? [.topics] : [.chats]
}
bottomInset += 44.0
let edgeEffectHeight: CGFloat = bottomInset + 8.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: ComponentTransition(transition))
transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0)
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: bottomInset, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition)
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height), sideInset: layout.safeInsets.left, topInset: topInset, bottomInset: bottomInset, visibleHeight: layout.size.height, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition)
}
private var currentMessages: ([EnginePeer.Id: EnginePeer], [EngineMessage.Id: EngineMessage]) {
@@ -1325,7 +1356,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
title = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertTitle(Int32(messages.count))
text = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertText(Int32(messages.count))
strongSelf.present?(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [
strongSelf.present?(textAlertController(context: strongSelf.context, title: title, text: text, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.DownloadList_RemoveFileAlertRemove, action: {
@@ -38,6 +38,7 @@ import MultilineTextComponent
import ButtonComponent
import BundleIconComponent
import AnimatedTextComponent
import TextFormat
private enum ChatListRecentEntryStableId: Hashable {
case topPeers
@@ -1696,7 +1697,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var emptyRecentAnimationNode: AnimatedStickerNode?
private var emptyRecentAnimationSize = CGSize()
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
private let ready = Promise<Bool>()
private var didSetReady: Bool = false
@@ -3495,7 +3496,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.interaction.openStories?(id, sourceNode.avatarNode)
}
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@@ -4508,7 +4508,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return
}
dismissImpl?()
if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
if let value = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
if !value.isEmpty {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: value, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: navigationController, dismissInput: {})
} else {
@@ -4536,7 +4536,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
)
interaction.present(alertController, nil)
dismissImpl = { [weak alertController] in
alertController?.dismissAnimated()
alertController?.dismiss()
}
},
isChannelsTabExpanded: recentItems.isChannelsTabExpanded,
@@ -4640,8 +4640,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.playlistStateAndType = nil
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
}
}
self.playlistLocation = playlistStateAndType?.1.playlistLocation
@@ -4758,10 +4758,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
let hadValidLayout = self.currentParams != nil
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.topInset != topInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
var topPanelHeight: CGFloat = 0.0
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType {
@@ -5035,9 +5035,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight)))
let topInset: CGFloat = topPanelHeight
let overflowInset: CGFloat = 20.0
let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
let topInset: CGFloat = topInset + topPanelHeight
let overflowInset: CGFloat = 0.0
let insets = UIEdgeInsets(top: topInset + topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height))
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, key: !(self.searchQueryValue?.isEmpty ?? true) && self.key == .media ? .chats : self.key, hasSelection: self.selectedMessages != nil, transition: transition)
@@ -5480,8 +5480,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.emptyResultsButtonSubtitleText = nil
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
}
if strongSelf.key == .downloads {
@@ -5783,7 +5783,6 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@@ -15,7 +15,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode {
var isReady: Signal<Bool, NoError> { get }
var isCurrent: Bool { get set }
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
func scrollToTop() -> Bool
func cancelPreviewGestures()
func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
@@ -32,21 +32,21 @@ final class ChatListSearchPaneWrapper {
let key: ChatListSearchPaneKey
let node: ChatListSearchPaneNode
var isAnimatingOut: Bool = false
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, PresentationData)?
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, PresentationData)?
init(key: ChatListSearchPaneKey, node: ChatListSearchPaneNode) {
self.key = key
self.node = node
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentTopInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentTopInset == topInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
return
}
}
self.appliedParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
self.appliedParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
self.node.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
}
}
@@ -190,7 +190,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
var isAdjacentLoadingEnabled = false
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
private(set) var currentPaneKey: ChatListSearchPaneKey?
var pendingSwitchToPaneKey: ChatListSearchPaneKey?
@@ -251,8 +251,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
if self.currentPanes[key] != nil {
self.currentPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
@@ -261,8 +261,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
} else if self.pendingSwitchToPaneKey != key {
self.pendingSwitchToPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
@@ -275,7 +275,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
super.didLoad()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let strongSelf = self, let (_, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
guard let strongSelf = self, let (_, _, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
return []
}
if index == 0 {
@@ -321,7 +321,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
cancelContextGestures(view: self.view)
case .changed:
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
self.isAdjacentLoadingEnabled = true
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / size.width
@@ -332,10 +332,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
}
case .cancelled, .ended:
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool?
@@ -364,7 +364,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
}
}
self.transitionFraction = 0.0
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
}
default:
break
@@ -396,7 +396,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
let previousAvailablePanes = self.currentAvailablePanes ?? []
self.currentAvailablePanes = availablePanes
@@ -430,7 +430,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
currentIndex = nil
}
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes)
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes)
switch self.location {
case .forum, .savedMessagesChats:
@@ -489,12 +489,12 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
guard let strongSelf = self else {
return
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
var transition: ContainedViewLayoutTransition = .immediate
if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil {
transition = .animated(duration: 0.4, curve: .spring)
}
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
}
}
if leftScope {
@@ -504,14 +504,14 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
)
self.pendingPanes[key] = pane
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
leftScope = true
}
}
for (key, pane) in self.pendingPanes {
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
if pane.isReady {
self.pendingPanes.removeValue(forKey: key)
@@ -587,7 +587,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
paneCompletion()
})
}
pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
pane.node.isCurrent = key == self.currentPaneKey
if paneWasAdded && key == self.currentPaneKey {
pane.node.didBecomeFocused()
@@ -598,7 +598,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
for (_, pane) in self.pendingPanes {
let paneTransition: ContainedViewLayoutTransition = .immediate
paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
}
if !self.didSetIsReady {
if let currentPaneKey = self.currentPaneKey, let currentPane = self.currentPanes[currentPaneKey] {
@@ -157,7 +157,6 @@ public final class ChatListShimmerNode: 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
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@@ -126,7 +126,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
self.maskNode = ASImageNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
}
override public func didLoad() {
@@ -172,7 +172,7 @@ class ChatListArchiveInfoItemNode: ListViewItemNode, ASScrollViewDelegate {
self.infoPageNodes = (0 ..< 3).map({ _ in InfoPageNode() })
self.pageControlNode.pagesCount = self.infoPageNodes.count
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.scrollNode)
self.infoPageNodes.forEach(self.scrollNode.addSubnode)
@@ -55,7 +55,7 @@ class ChatListEmptyHeaderItemNode: ListViewItemNode {
private var item: ChatListEmptyHeaderItem?
required init() {
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@@ -92,7 +92,7 @@ class ChatListEmptyInfoItemNode: ListViewItemNode {
self.animationNode = DefaultAnimatedStickerNodeImpl()
self.textNode = TextNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.animationNode)
self.addSubnode(self.textNode)
@@ -207,7 +207,7 @@ class ChatListSectionHeaderNode: ListViewItemNode {
private var headerNode: ListSectionHeaderNode?
required init() {
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.zPosition = 1.0
}
@@ -56,7 +56,7 @@ class ChatListHoleItemNode: ListViewItemNode {
var relativePosition: (first: Bool, last: Bool) = (false, false)
required init() {
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@@ -153,7 +153,7 @@ class ChatListSearchEmptyFooterItemNode: ListViewItemNode {
self.searchAllMessagesTitle = TextNode()
self.searchAllMessagesTitle.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.contentNode)
self.contentNode.addSubnode(self.titleNode)
@@ -15,7 +15,6 @@ import PeerOnlineMarkerNode
import LocalizedPeerData
import PeerPresenceStatusManager
import PhotoResources
import ChatListSearchItemNode
import ContextUI
import ChatInterfaceState
import TextFormat
@@ -219,6 +218,7 @@ public enum ChatListItemContent {
public var message: EngineMessage?
public var unreadCount: Int
public var hiddenByDefault: Bool
public var appearsPinned: Bool
public var storyState: StoryState?
public init(
@@ -227,6 +227,7 @@ public enum ChatListItemContent {
message: EngineMessage?,
unreadCount: Int,
hiddenByDefault: Bool,
appearsPinned: Bool,
storyState: StoryState?
) {
self.groupId = groupId
@@ -234,6 +235,7 @@ public enum ChatListItemContent {
self.message = message
self.unreadCount = unreadCount
self.hiddenByDefault = hiddenByDefault
self.appearsPinned = appearsPinned
self.storyState = storyState
}
}
@@ -454,7 +456,7 @@ private final class ChatListItemTagListComponent: Component {
}
}
public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
public class ChatListItem: ListViewItem {
public enum EnabledContextActions {
public struct Actions: OptionSet {
public var rawValue: Int32
@@ -1472,7 +1474,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else {
result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage
}
let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
let (_, initialHideAuthor, messageText, _, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author {
result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)"
}
@@ -1506,7 +1508,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else {
result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage
}
let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: peerData.messages, chatPeer: peerData.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
let (_, initialHideAuthor, messageText, _, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: peerData.messages, chatPeer: peerData.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false)
if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author {
result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)"
}
@@ -1656,7 +1658,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
super.init(layerBacked: false, rotated: false, seeThrough: false)
self.isAccessibilityElement = true
@@ -2452,7 +2454,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = params.leftInset + avatarLeftInset
enum ContentData {
case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, messageEntities: [MessageTextEntity], spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case group(peers: [EngineChatList.GroupItem.Item])
}
@@ -2461,7 +2463,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var hideAuthor = false
switch contentPeer {
case let .chat(itemPeer):
var (peer, initialHideAuthor, messageText, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
var (peer, initialHideAuthor, messageText, messageEntities, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup)
if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText {
initialHideAuthor = true
@@ -2489,7 +2491,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
break
}
contentData = .chat(itemPeer: itemPeer, threadInfo: threadInfo, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers, customEmojiRanges: customEmojiRanges)
contentData = .chat(itemPeer: itemPeer, threadInfo: threadInfo, peer: peer, hideAuthor: hideAuthor, messageText: messageText, messageEntities: messageEntities, spoilers: spoilers, customEmojiRanges: customEmojiRanges)
hideAuthor = initialHideAuthor
case let .group(groupPeers):
contentData = .group(peers: groupPeers)
@@ -2508,7 +2510,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
forumTopicData = nil
topForumTopicItems = []
if case let .chat(itemPeer, _, _, _, _, _, _) = contentData {
if case let .chat(itemPeer, _, _, _, _, _, _, _) = contentData {
if let messagePeer = itemPeer.chatMainPeer {
switch messagePeer {
case let .channel(channel):
@@ -2556,7 +2558,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var ignoreForwardedIcon = false
switch contentData {
case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges):
case let .chat(itemPeer, _, _, _, text, entities, spoilers, customEmojiRanges):
var isUser = false
if case .user = itemPeer.chatMainPeer {
isUser = true
@@ -2639,7 +2641,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
chatListText = (text, messageText)
}
if inlineAuthorPrefix == nil, let mediaDraftContentType {
hasDraft = true
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
@@ -2671,8 +2673,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peerText = peerText {
authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor)
}
var entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
var entities = entities.filter { entity in
switch entity.type {
case .Spoiler, .CustomEmoji:
return true
@@ -2690,14 +2692,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else {
regex = loginCodeRegex
}
if let cached = currentCustomTextEntities, cached.matches(text: message.text) {
if let cached = currentCustomTextEntities, cached.matches(text: messageText) {
customTextEntities = cached
} else if let matches = regex?.matches(in: message.text, options: [], range: NSMakeRange(0, (message.text as NSString).length)) {
} else if let matches = regex?.matches(in: messageText, options: [], range: NSMakeRange(0, (messageText as NSString).length)) {
var entities: [MessageTextEntity] = []
if let first = matches.first {
entities.append(MessageTextEntity(range: first.range.location ..< first.range.location + first.range.length, type: .Spoiler))
}
customTextEntities = CachedCustomTextEntities(text: message.text, textEntities: entities)
customTextEntities = CachedCustomTextEntities(text: messageText, textEntities: entities)
}
}
@@ -2706,14 +2708,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
let messageString: NSAttributedString
if !message.text.isEmpty && entities.count > 0 {
var messageText = message.text
var entities = entities
if !"".isEmpty, let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !translation.text.isEmpty {
messageText = translation.text
entities = translation.entities
}
if !messageText.isEmpty && entities.count > 0 {
messageString = foldLineBreaks(stringWithAppliedEntities(messageText, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage()))
} else if spoilers != nil || customEmojiRanges != nil {
let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
@@ -3100,7 +3095,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
switch contentData {
case let .chat(itemPeer, threadInfo, _, _, _, _, _):
case let .chat(itemPeer, threadInfo, _, _, _, _, _, _):
if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData {
if customMessageListData.commandPrefix != nil {
titleAttributedString = nil
@@ -3854,6 +3849,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
transition = .immediate
}
transition.updateAlpha(node: strongSelf, alpha: item.hiddenOffset ? 0.0 : 1.0)
ComponentTransition(transition).setBlur(layer: strongSelf.layer, radius: item.hiddenOffset ? 8.0 : 0.0)
let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight))
// strongSelf.contextContainer.position = contextContainerFrame.center
transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center)
@@ -5031,7 +5029,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault {
separatorInset = 0.0
} else if (!nextIsPinned && isPinned) || last {
separatorInset = 0.0
separatorInset = 0.0
} else {
separatorInset = editingOffset + leftInset + rawContentRect.origin.x
}
@@ -5057,8 +5055,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
highlightedBackgroundColor = theme.itemHighlightedBackgroundColor
} else if isPinned {
if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault {
backgroundColor = theme.itemBackgroundColor
highlightedBackgroundColor = theme.itemHighlightedBackgroundColor
backgroundColor = groupReferenceData.appearsPinned ? theme.pinnedItemBackgroundColor : theme.itemBackgroundColor
highlightedBackgroundColor = groupReferenceData.appearsPinned ? theme.pinnedItemHighlightedBackgroundColor : theme.itemHighlightedBackgroundColor
} else {
backgroundColor = theme.pinnedItemBackgroundColor
highlightedBackgroundColor = theme.pinnedItemHighlightedBackgroundColor
@@ -77,20 +77,21 @@ private func paidContentGroupType(paidContent: TelegramMediaPaidContent) -> Mess
return currentType
}
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, contentSettings: ContentSettings, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) {
public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, contentSettings: ContentSettings, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, messageEntities: [MessageTextEntity], spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) {
let peer: EnginePeer?
let message = messages.last
if let restrictionReason = message?._asMessage().restrictionReason(platform: "ios", contentSettings: contentSettings) {
return (nil, false, restrictionReason, nil, nil)
return (nil, false, restrictionReason, [], nil, nil)
}
if let restrictionReason = chatPeer.chatMainPeer?.restrictionText(platform: "ios", contentSettings: contentSettings) {
return (nil, false, restrictionReason, nil, nil)
return (nil, false, restrictionReason, [], nil, nil)
}
var hideAuthor = false
var messageText: String
var messageEntities: [MessageTextEntity] = []
var spoilers: [NSRange]?
var customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?
if let message = message {
@@ -104,6 +105,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
for message in messages {
if !message.text.isEmpty {
messageText = message.text
messageEntities = message._asMessage().textEntitiesAttribute?.entities ?? []
break
}
}
@@ -469,5 +471,5 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
}
}
return (peer, hideAuthor, messageText, spoilers, customEmojiRanges)
return (peer, hideAuthor, messageText, messageEntities, spoilers, customEmojiRanges)
}
@@ -23,6 +23,7 @@ import ChatListHeaderComponent
import UndoUI
import NewSessionInfoScreen
import PresentationDataUtils
import GlobalControlPanelsContext
public enum ChatListNodeMode {
case chatList(appendContacts: Bool)
@@ -110,7 +111,6 @@ public final class ChatListNodeInteraction {
let hideChatFolderUpdates: () -> Void
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
let openStarsTopup: (Int64?) -> Void
let dismissNotice: (ChatListNotice) -> Void
let editPeer: (ChatListItem) -> Void
let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void
@@ -171,7 +171,6 @@ public final class ChatListNodeInteraction {
hideChatFolderUpdates: @escaping () -> Void,
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void,
openStarsTopup: @escaping (Int64?) -> Void,
dismissNotice: @escaping (ChatListNotice) -> Void,
editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void,
@@ -219,7 +218,6 @@ public final class ChatListNodeInteraction {
self.hideChatFolderUpdates = hideChatFolderUpdates
self.openStories = openStories
self.openStarsTopup = openStarsTopup
self.dismissNotice = dismissNotice
self.editPeer = editPeer
self.openWebApp = openWebApp
self.openPhotoSetup = openPhotoSetup
@@ -698,6 +696,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
message: groupReferenceEntry.message,
unreadCount: groupReferenceEntry.unreadCount,
hiddenByDefault: groupReferenceEntry.hiddenByDefault,
appearsPinned: groupReferenceEntry.appearsPinned,
storyState: groupReferenceEntry.storyState.flatMap { storyState in
return ChatListItemContent.StoryState(
stats: storyState.stats,
@@ -751,47 +750,6 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {
case .clearStorage:
nodeInteraction?.openStorageManagement()
case .setupPassword:
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift:
nodeInteraction?.openPremiumGift([], nil)
case .premiumGrace:
nodeInteraction?.openPremiumManagement()
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
nodeInteraction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
nodeInteraction?.openUrl(url)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
case let .buttonChoice(isPositive):
switch notice {
case let .reviewLogin(newSessionReview, _):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
}), directionHint: entry.directionHint)
}
}
}
@@ -1048,6 +1006,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
message: groupReferenceEntry.message,
unreadCount: groupReferenceEntry.unreadCount,
hiddenByDefault: groupReferenceEntry.hiddenByDefault,
appearsPinned: groupReferenceEntry.appearsPinned,
storyState: groupReferenceEntry.storyState.flatMap { storyState in
return ChatListItemContent.StoryState(
stats: storyState.stats,
@@ -1101,47 +1060,6 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {
case .clearStorage:
nodeInteraction?.openStorageManagement()
case .setupPassword:
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift:
nodeInteraction?.openPremiumGift([], nil)
case .premiumGrace:
nodeInteraction?.openPremiumManagement()
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
nodeInteraction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
nodeInteraction?.openUrl(url)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
case let .buttonChoice(isPositive):
switch notice {
case let .reviewLogin(newSessionReview, _):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
}), directionHint: entry.directionHint)
case .HeaderEntry:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
@@ -1282,7 +1200,7 @@ public final class ChatListNode: ListView {
return []
}
}
private var interaction: ChatListNodeInteraction?
public private(set) var interaction: ChatListNodeInteraction?
private var dequeuedInitialTransitionOnLayout = false
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
@@ -1383,7 +1301,6 @@ public final class ChatListNode: ListView {
private let autoSetReady: Bool
public let isMainTab = ValuePromise<Bool>(false, ignoreRepeated: true)
private let suggestedChatListNotice = Promise<ChatListNotice?>(nil)
public var synchronousDrawingWhenNotAnimated: Bool = false
@@ -1868,38 +1785,6 @@ public final class ChatListNode: ListView {
return
}
self.openStarsTopup?(amount)
}, dismissNotice: { [weak self] notice in
guard let self else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
switch notice {
case .xmasPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .setupBirthday:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .birthdayPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .premiumGrace:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone()
case .setupPhoto:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone()
case .starsSubscriptionLowBalance:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone()
case let .link(id, _, _, _):
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone()
default:
break
}
}, editPeer: { _ in
}, openWebApp: { [weak self] user in
guard let self else {
@@ -1992,172 +1877,12 @@ public final class ChatListNode: ListView {
} else {
displayArchiveIntro = .single(false)
}
let starsSubscriptionsContextPromise = Promise<StarsSubscriptionsContext?>(nil)
self.updateIsMainTabDisposable = (self.isMainTab.get()
|> deliverOnMainQueue).startStrict(next: { [weak self] isMainTab in
guard let self else {
return
|> deliverOnMainQueue).startStrict(next: { isMainTab in
if isMainTab {
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
}
guard case .chatList(groupId: .root) = location, isMainTab else {
self.suggestedChatListNotice.set(.single(nil))
return
}
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> AccountFreezeConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
})
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(),
twoStepData,
newSessionReviews(postbox: context.account.postbox),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
),
context.account.stateManager.contactBirthdays,
starsSubscriptionsContextPromise.get(),
accountFreezeConfiguration
)
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
}
if suggestions.contains(.setupPassword), let configuration {
var notSet = false
switch configuration {
case let .notSet(pendingEmail):
if pendingEmail == nil {
notSet = true
}
case .set:
break
}
if notSet {
return .single(.setupPassword)
}
}
let today = Calendar(identifier: .gregorian).component(.day, from: Date())
var todayBirthdayPeerIds: [EnginePeer.Id] = []
for (peerId, birthday) in birthdays {
if birthday.day == today {
todayBirthdayPeerIds.append(peerId)
}
}
todayBirthdayPeerIds.sort { lhs, rhs in
return lhs < rhs
}
if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) {
todayBirthdayPeerIds = []
}
if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
return .starsSubscriptionLowBalance(
amount: state.balance,
peers: state.subscriptions.map { $0.peer }
)
} else {
return nil
}
}
} else {
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
return .single(nil)
}
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
return .single(.setupPhoto(accountPeer))
} else if suggestions.contains(.gracePremium) {
return .single(.premiumGrace)
} else if suggestions.contains(.xmasPremiumGift) {
return .single(.xmasPremiumGift)
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
return inAppPurchaseManager.availableProducts
|> map { products -> ChatListNotice? in
if products.count > 1 {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
for product in products {
if product.id.hasSuffix(".annual") {
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
if discount > 0 {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: discount)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: discount)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: discount)
}
}
break
}
}
return nil
} else {
if !GlobalExperimentalSettings.isAppStoreBuild {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: 0)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: 0)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: 0)
}
}
return nil
}
}
} else if !todayBirthdayPeerIds.isEmpty {
return context.engine.data.get(
EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> map { result -> ChatListNotice? in
var todayBirthdayPeers: [EnginePeer] = []
for (peerId, _) in birthdays {
if let maybePeer = result[peerId], let peer = maybePeer {
todayBirthdayPeers.append(peer)
}
}
return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays)
}
} else if suggestions.contains(.setupBirthday) && birthday == nil {
return .single(.setupBirthday)
} else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) {
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
} else {
return .single(nil)
}
}
|> distinctUntilChanged
self.suggestedChatListNotice.set(suggestedChatListNoticeSignal)
}).strict()
let storageInfo: Signal<Double?, NoError>
@@ -2346,7 +2071,6 @@ public final class ChatListNode: ListView {
hideArchivedFolderByDefault,
displayArchiveIntro,
storageInfo,
suggestedChatListNotice.get(),
savedMessagesPeer,
chatListViewUpdate,
self.statePromise.get(),
@@ -2354,23 +2078,14 @@ public final class ChatListNode: ListView {
chatListFilters,
accountIsPremium
)
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
let notice: ChatListNotice?
if let suggestedChatListNotice {
notice = suggestedChatListNotice
} else if let storageInfo {
notice = .clearStorage(sizeFraction: storageInfo)
} else {
notice = nil
}
let innerIsMainTab = location == .chatList(groupId: .root) && chatListFilter == nil
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
var isEmpty = true
var entries = rawEntries.filter { entry in
switch entry {
@@ -2697,7 +2412,6 @@ public final class ChatListNode: ListView {
var didIncludeRemovingPeerId = false
var didIncludeHiddenByDefaultArchive = false
var didIncludeHiddenThread = false
var didIncludeNotice = false
if let previous = previousView {
for entry in previous.filteredEntries {
if case let .PeerEntry(peerEntry) = entry {
@@ -2724,15 +2438,12 @@ public final class ChatListNode: ListView {
}
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
didIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
} else if case .Notice = entry {
didIncludeNotice = true
}
}
}
var doesIncludeRemovingPeerId = false
var doesIncludeArchive = false
var doesIncludeHiddenByDefaultArchive = false
var doesIncludeNotice = false
var doesIncludeHiddenThread = false
for entry in processedView.filteredEntries {
@@ -2761,8 +2472,6 @@ public final class ChatListNode: ListView {
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
doesIncludeArchive = true
doesIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
} else if case .Notice = entry {
doesIncludeNotice = true
}
}
if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads {
@@ -2789,9 +2498,6 @@ public final class ChatListNode: ListView {
if didIncludeHiddenThread != doesIncludeHiddenThread {
disableAnimations = false
}
if didIncludeNotice != doesIncludeNotice {
disableAnimations = false
}
}
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
@@ -3547,8 +3253,10 @@ public final class ChatListNode: ListView {
if entryCount - 1 - i < 0 {
continue
}
if case .PeerEntry = transition.chatListView.filteredEntries[entryCount - 1 - i] {
} else {
switch transition.chatListView.filteredEntries[entryCount - 1 - i] {
case .PeerEntry, .GroupReferenceEntry:
break
default:
continue
}
if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1 - i].sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex != nil {
@@ -3658,7 +3366,7 @@ public final class ChatListNode: ListView {
} else {
break loop
}
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory:
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory:
break
}
}
@@ -79,23 +79,6 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
case psa(type: String, message: String?)
}
public enum ChatListNotice: Equatable {
case clearStorage(sizeFraction: Double)
case setupPassword
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32)
case xmasPremiumGift
case setupBirthday
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer)
case accountFreeze
case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text)
}
enum ChatListNodeEntry: Comparable, Identifiable {
struct PeerEntryData: Equatable {
var index: EngineChatList.Item.Index
@@ -339,6 +322,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
var unreadCount: Int
var revealed: Bool
var hiddenByDefault: Bool
var appearsPinned: Bool
var storyState: ChatListNodeState.StoryState?
init(
@@ -351,6 +335,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
unreadCount: Int,
revealed: Bool,
hiddenByDefault: Bool,
appearsPinned: Bool,
storyState: ChatListNodeState.StoryState?
) {
self.index = index
@@ -362,6 +347,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
self.unreadCount = unreadCount
self.revealed = revealed
self.hiddenByDefault = hiddenByDefault
self.appearsPinned = appearsPinned
self.storyState = storyState
}
@@ -393,6 +379,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
if lhs.hiddenByDefault != rhs.hiddenByDefault {
return false
}
if lhs.appearsPinned != rhs.appearsPinned {
return false
}
if lhs.storyState != rhs.storyState {
return false
}
@@ -409,7 +398,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
case ArchiveIntro(presentationData: ChatListPresentationData)
case EmptyIntro(presentationData: ChatListPresentationData)
case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
var sortIndex: ChatListNodeEntrySortIndex {
@@ -430,8 +418,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .SectionHeader:
return .sectionHeader
case .Notice:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _):
return .additionalCategory(index)
}
@@ -460,8 +446,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .EmptyIntro
case .SectionHeader:
return .SectionHeader
case .Notice:
return .Notice
case let .AdditionalCategory(_, id, _, _, _, _, _):
return .additionalCategory(id)
}
@@ -534,18 +518,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case let .Notice(lhsPresentationData, lhsInfo):
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsInfo != rhsInfo {
return false
}
return true
} else {
return false
}
case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData):
if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs {
if lhsIndex != rhsIndex {
@@ -595,7 +567,7 @@ struct ChatListContactPeer {
}
}
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) {
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) {
var groupItems = view.groupItems
if isMainTab && state.archiveStoryState != nil && groupItems.isEmpty {
groupItems.append(EngineChatList.GroupItem(
@@ -656,6 +628,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
var hiddenGeneralThread: ChatListNodeEntry?
var hasPinned = false
loop: for entry in view.items {
var peerId: EnginePeer.Id?
var threadId: Int64?
@@ -707,6 +681,17 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
if let threadData = entry.threadData, let threadId {
threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden, threadPeer: nil)
}
switch entry.index {
case let .chatList(chatList):
if chatList.pinningIndex != nil {
hasPinned = true
}
case let .forum(pinnedIndex, _, _, _, _):
if case .index = pinnedIndex {
hasPinned = true
}
}
let entry: ChatListNodeEntry = .PeerEntry(ChatListNodeEntry.PeerEntryData(
index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset),
@@ -796,6 +781,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
)))
if foundPinningIndex != 0 {
foundPinningIndex -= 1
hasPinned = true
}
}
}
@@ -886,6 +872,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
)))
if pinningIndex != 0 {
pinningIndex -= 1
hasPinned = true
}
}
}
@@ -908,10 +895,12 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
unreadCount: groupReference.unreadCount,
revealed: state.hiddenItemShouldBeTemporaryRevealed,
hiddenByDefault: hideArchivedFolderByDefault,
appearsPinned: hasPinned,
storyState: mappedStoryState
)))
if pinningIndex != 0 {
pinningIndex -= 1
hasPinned = true
}
}
@@ -927,10 +916,6 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
result.append(.EmptyIntro(presentationData: state.presentationData))
}
if let notice {
result.append(.Notice(presentationData: state.presentationData, notice: notice))
}
result.append(.HeaderEntry)
}