diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 62a9ed76e5..23eb11f7a3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9041,3 +9041,14 @@ Sorry for the inconvenience."; "Premium.Gift.TitleShort" = "Telegram Premium"; "VoiceOver.GiftPremium" = "Gift Telegram Premium"; + +"Login.Email.CantAccess" = "Can't access this email?"; +"Login.Email.ResetTitle" = "Reset Email"; +"Login.Email.ResetText" = "You can change your login email if you are logged into Telegram from another device. Otherwise, if you don't have access to email %@, you can reset this email with an SMS code in 7 days."; +"Login.Email.Reset" = "Reset"; +"Login.Email.ResetNowViaSMS" = "Reset now via SMS"; +"Login.Email.WillBeResetIn" = "Email will be reset in %@"; +"Login.Email.PremiumRequiredTitle" = "Telegram Premium Required"; +"Login.Email.PremiumRequiredText" = "Due to high cost of SMS in your country, you need to have a **Telegram Premium** account to reset this email via an SMS code. You can ask a friend to a gift a Premium subscription for your account %@"; + +"ChatList.StartMessaging" = "Select a chat to start messaging"; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index ff22529156..840fce621a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -433,7 +433,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let containerFrame: CGRect let clipFrame: CGRect let containerScale: CGFloat - if layout.metrics.widthClass == .compact { + if case .compact = layout.metrics.widthClass { self.clipNode.clipsToBounds = true if isLandscape { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c6d0ea6110..febbaaa2a5 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -4811,7 +4811,7 @@ private final class ChatListLocationContext { strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, - content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil), + content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), tapped: { [weak self] in guard let self else { return diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 88dffb0d48..4668c529a8 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -34,11 +34,6 @@ public struct NavigationAnimationOptions : OptionSet { public static let removeOnMasterDetails = NavigationAnimationOptions(rawValue: 1 << 0) } -public enum NavigationEmptyDetailsBackgoundMode { - case image(UIImage) - case wallpaper(UIImage) -} - private enum ControllerTransition { case none case appearance @@ -120,6 +115,10 @@ public final class NavigationControllerDropContent { } } +public protocol NavigationDetailsPlaceholderNode: ASDisplayNode { + func updateLayout(size: CGSize, needsTiling: Bool, transition: ContainedViewLayoutTransition) +} + open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { public var isOpaqueWhenInOverlay: Bool = true public var blocksBackgroundWhenInOverlay: Bool = true @@ -131,7 +130,6 @@ open class NavigationController: UINavigationController, ContainableController, } private var masterDetailsBlackout: MasterDetailLayoutBlackout? - private var backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? public var lockOrientation: Bool = false @@ -232,16 +230,21 @@ open class NavigationController: UINavigationController, ContainableController, self.requestLayout(transition: transition) } - public func updateBackgroundDetailsMode(_ mode: NavigationEmptyDetailsBackgoundMode?, transition: ContainedViewLayoutTransition) { - self.backgroundDetailsMode = mode - self.requestLayout(transition: transition) + private weak var detailsPlaceholderNode: NavigationDetailsPlaceholderNode? + public func updateDetailsPlaceholderNode(_ node: NavigationDetailsPlaceholderNode?) { + if self.detailsPlaceholderNode !== node { + self.detailsPlaceholderNode?.removeFromSupernode() + self.detailsPlaceholderNode = node + if let node { + self.displayNode.insertSubnode(node, at: 0) + } + } } - public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, isFlat: Bool = false, backgroundDetailsMode: NavigationEmptyDetailsBackgoundMode? = nil) { + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme, isFlat: Bool = false) { self.mode = mode self.theme = theme self.isFlat = isFlat - self.backgroundDetailsMode = backgroundDetailsMode super.init(nibName: nil, bundle: nil) } @@ -340,7 +343,7 @@ open class NavigationController: UINavigationController, ContainableController, return nil } - public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if !self.isViewLoaded { self.loadView() } @@ -836,7 +839,11 @@ open class NavigationController: UINavigationController, ContainableController, flatContainer.keyboardViewManager = nil flatContainer.canHaveKeyboardFocus = false } - self.displayNode.insertSubnode(flatContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(flatContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(flatContainer, at: 0) + } self.rootContainer = .flat(flatContainer) flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) @@ -859,7 +866,11 @@ open class NavigationController: UINavigationController, ContainableController, flatContainer.keyboardViewManager = nil flatContainer.canHaveKeyboardFocus = false } - self.displayNode.insertSubnode(flatContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(flatContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(flatContainer, at: 0) + } self.rootContainer = .flat(flatContainer) flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size) flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate) @@ -873,7 +884,11 @@ open class NavigationController: UINavigationController, ContainableController, }, scrollToTop: { [weak self] subject in self?.scrollToTop(subject) }) - self.displayNode.insertSubnode(splitContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(splitContainer, at: 0) + } self.rootContainer = .split(splitContainer) if previousModalContainer == nil { splitContainer.canHaveKeyboardFocus = true @@ -881,7 +896,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: .immediate) flatContainer.statusBarStyleUpdated = nil flatContainer.removeFromSupernode() case let .split(splitContainer): @@ -891,7 +906,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } transition.updateFrame(node: splitContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: transition) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: transition) } } else { let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in @@ -899,7 +914,11 @@ open class NavigationController: UINavigationController, ContainableController, }, scrollToTop: { [weak self] subject in self?.scrollToTop(subject) }) - self.displayNode.insertSubnode(splitContainer, at: 0) + if let detailsPlaceholderNode = self.detailsPlaceholderNode { + self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) + } else { + self.displayNode.insertSubnode(splitContainer, at: 0) + } self.rootContainer = .split(splitContainer) if previousModalContainer == nil { splitContainer.canHaveKeyboardFocus = true @@ -907,7 +926,7 @@ open class NavigationController: UINavigationController, ContainableController, splitContainer.canHaveKeyboardFocus = false } splitContainer.frame = CGRect(origin: CGPoint(), size: layout.size) - splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, transition: .immediate) + splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: .immediate) } } diff --git a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift index 809df60a31..1f1f795837 100644 --- a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift @@ -83,7 +83,7 @@ final class NavigationSplitContainer: ASDisplayNode { self.separator.backgroundColor = theme.navigationBar.separatorColor } - func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], transition: ContainedViewLayoutTransition) { + func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], detailsPlaceholderNode: NavigationDetailsPlaceholderNode?, transition: ContainedViewLayoutTransition) { let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let detailWidth = layout.size.width - masterWidth @@ -94,6 +94,12 @@ final class NavigationSplitContainer: ASDisplayNode { transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + if let detailsPlaceholderNode { + let needsTiling = layout.size.width > layout.size.height + detailsPlaceholderNode.updateLayout(size: CGSize(width: detailWidth, height: layout.size.height), needsTiling: needsTiling, transition: transition) + transition.updateFrame(node: detailsPlaceholderNode, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) + } + self.masterContainer.update(layout: ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: false, controllers: masterControllers, transition: transition) self.detailContainer.update(layout: ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), canBeClosed: true, controllers: detailControllers, transition: transition) diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index 59b5e3965f..b5e3fe6c42 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -271,6 +271,22 @@ public final class GradientBackgroundNode: ASDisplayNode { private var patternOverlayLayer: GradientBackgroundPatternOverlayLayer? + private class SharedAnimationUpdate { + let phase: Int + let sender: AnyObject + + init( + phase: Int, + sender: AnyObject + ) { + self.phase = phase + self.sender = sender + } + } + + private static let sharedAnimationSyncPipe = ValuePipe() + private var sharedAnimationSyncDisposable: Disposable? + public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false, adjustSaturation: Bool = true) { self.useSharedAnimationPhase = useSharedAnimationPhase self.saturation = adjustSaturation ? 1.7 : 1.0 @@ -289,12 +305,26 @@ public final class GradientBackgroundNode: ASDisplayNode { if useSharedAnimationPhase { self.phase = GradientBackgroundNode.sharedPhase + + self.sharedAnimationSyncDisposable = (GradientBackgroundNode.sharedAnimationSyncPipe.signal() + |> filter { [weak self] update in + return update.sender !== self + } + |> deliverOnMainQueue).start(next: { [weak self] update in + if let self { + self.phase = update.phase + if let size = self.validLayout { + self.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {}) + } + } + }) } else { self.phase = 0 } } deinit { + self.sharedAnimationSyncDisposable?.dispose() } public func setPatternOverlay(layer: GradientBackgroundPatternOverlayLayer?) { @@ -422,8 +452,7 @@ public final class GradientBackgroundNode: ASDisplayNode { animation.fillMode = .backwards animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25 } - - + self.isAnimating = true if let patternOverlayLayer = self.patternOverlayLayer { patternOverlayLayer.isAnimating = true @@ -542,6 +571,8 @@ public final class GradientBackgroundNode: ASDisplayNode { } } } + + public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) { guard case let .animated(duration, _) = transition, duration > 0.001 else { @@ -560,6 +591,7 @@ public final class GradientBackgroundNode: ASDisplayNode { } if self.useSharedAnimationPhase { GradientBackgroundNode.sharedPhase = self.phase + GradientBackgroundNode.sharedAnimationSyncPipe.putNext(SharedAnimationUpdate(phase: self.phase, sender: self)) } if let size = self.validLayout { self.updateLayout(size: size, transition: transition, extendAnimation: extendAnimation, backwards: backwards, completion: completion) diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index 913ff045ad..51037faef1 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -287,9 +287,7 @@ public class InvisibleInkDustNode: ASDisplayNode { return } self.staticParams = (size, color, lineRects) - - let start = CACurrentMediaTime() - + var combinedRect: CGRect? var combinedRects: [CGRect] = [] for rect in lineRects { @@ -308,7 +306,6 @@ public class InvisibleInkDustNode: ASDisplayNode { combinedRects.append(combinedRect.insetBy(dx: 0.0, dy: -1.0)) } - print("combining \(CACurrentMediaTime() - start)") Queue.concurrentDefaultQueue().async { var generator = ArbitraryRandomNumberGenerator(seed: 1) let image = generateImage(size, rotatedContext: { size, context in @@ -331,8 +328,6 @@ public class InvisibleInkDustNode: ASDisplayNode { } } self.staticNode?.frame = CGRect(origin: CGPoint(), size: size) - - print("total draw \(CACurrentMediaTime() - start)") } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 0c53822d39..d8eff66339 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -32,14 +32,14 @@ public enum ChatTitleContent: Equatable { case replies } - case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?) + case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) case replyThread(type: ReplyThreadType, count: Int) case custom(String, String?, Bool) public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { switch lhs { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount): - if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount) = rhs { + case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled): + if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs { if peerView !== rhsPeerView { return false } @@ -58,7 +58,9 @@ public enum ChatTitleContent: Equatable { if customMessageCount != rhsCustomMessageCount { return false } - + if isEnabled != rhsIsEnabled { + return false + } return true } else { return false @@ -169,7 +171,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleCredibilityIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { - case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _): + case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _, isEnabledValue): if peerView.peerId.isReplies { let typeText: String = self.strings.DialogList_Replies segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] @@ -225,6 +227,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } } + isEnabled = isEnabledValue } case let .replyThread(type, count): let textFont = titleFont @@ -365,7 +368,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount): + case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount, _): if let peer = peerViewMainPeer(peerView) { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { inputActivitiesAllowed = false @@ -469,7 +472,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount): + case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): if let customMessageCount = customMessageCount, customMessageCount != 0 { let string = NSAttributedString(string: self.strings.Conversation_Messages(Int32(customMessageCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2058ece78f..e8446f3402 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4670,9 +4670,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + let hasPeerInfo: Signal + if peerId == context.account.peerId { + hasPeerInfo = .single(true) + |> then( + hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId) + ) + } else { + hasPeerInfo = .single(true) + } - self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState in + self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo in if let strongSelf = self { var isScheduledMessages = false if case .scheduledMessages = presentationInterfaceState.subject { @@ -4723,7 +4732,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .pinnedMessages = presentationInterfaceState.subject { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) let imageOverride: AvatarNodeImageOverride? if strongSelf.context.account.peerId == peer.id { imageOverride = .savedMessagesIcon @@ -5278,7 +5287,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let threadInfo = messageAndTopic.threadData?.info { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) let avatarContent: EmojiStatusComponent.Content if strongSelf.chatLocation.threadId == 1 { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 44d8a0bee3..cf161a5fad 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -801,7 +801,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.emptyNode = emptyNode self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) if let (size, insets) = self.validEmptyNodeLayout { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) } if animated { emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1622,7 +1622,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { emptyNodeInsets.bottom += inputPanelsHeight self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets) if let emptyNode = self.emptyNode, let emptyType = self.emptyType { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) transition.updateFrame(node: emptyNode, frame: contentBounds) emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 03d0288b4f..7020896e47 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -16,7 +16,7 @@ import ComponentFlow import EmojiStatusComponent private protocol ChatEmptyNodeContent { - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize } private let titleFont = Font.medium(15.0) @@ -36,7 +36,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod self.addSubnode(self.textNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -44,12 +44,16 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) let text: String - switch interfaceState.chatLocation { - case .peer, .replyThread, .feed: - if case .scheduledMessages = interfaceState.subject { - text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder - } else { - text = interfaceState.strings.Conversation_EmptyPlaceholder + if case .detailsPlaceholder = subject { + text = interfaceState.strings.ChatList_StartMessaging + } else { + switch interfaceState.chatLocation { + case .peer, .replyThread, .feed: + if case .scheduledMessages = interfaceState.subject { + text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder + } else { + text = interfaceState.strings.Conversation_EmptyPlaceholder + } } } @@ -140,7 +144,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -309,7 +313,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -442,7 +446,7 @@ private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNode self.addSubnode(self.subtitleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -576,7 +580,7 @@ private final class ChatEmptyNodeGroupChatContent: ASDisplayNode, ChatEmptyNodeC self.addSubnode(self.subtitleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -691,7 +695,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC self.addSubnode(self.titleNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -813,7 +817,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, self.addSubnode(self.textNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme @@ -900,6 +904,10 @@ private enum ChatEmptyNodeContentType: Equatable { } final class ChatEmptyNode: ASDisplayNode { + enum Subject { + case emptyChat(ChatHistoryNodeLoadState.EmptyType) + case detailsPlaceholder + } private let context: AccountContext private let interaction: ChatPanelInterfaceInteraction? @@ -953,7 +961,7 @@ final class ChatEmptyNode: ASDisplayNode { } } - func updateLayout(interfaceState: ChatPresentationInterfaceState, emptyType: ChatHistoryNodeLoadState.EmptyType, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { self.wallpaperBackgroundNode = backgroundNode if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { @@ -969,38 +977,43 @@ final class ChatEmptyNode: ASDisplayNode { } let contentType: ChatEmptyNodeContentType - if case .replyThread = interfaceState.chatLocation { - if case .topic = emptyType { - contentType = .topic - } else { - contentType = .regular - } - } else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages { - if peer.id == self.context.account.peerId { - contentType = .cloud - } else if let _ = peer as? TelegramSecretChat { - contentType = .secret - } else if let group = peer as? TelegramGroup, case .creator = group.role { - contentType = .group - } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) { - contentType = .group - } else if let _ = interfaceState.peerNearbyData { - contentType = .peerNearby - } else if let peer = peer as? TelegramUser { - if peer.isDeleted || peer.botInfo != nil || peer.flags.contains(.isSupport) || peer.isScam || interfaceState.peerIsBlocked { - contentType = .regular - } else if case .clearedHistory = emptyType { - contentType = .regular + switch subject { + case .detailsPlaceholder: + contentType = .regular + case let .emptyChat(emptyType): + if case .replyThread = interfaceState.chatLocation { + if case .topic = emptyType { + contentType = .topic } else { - contentType = .greeting + contentType = .regular + } + } else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages { + if peer.id == self.context.account.peerId { + contentType = .cloud + } else if let _ = peer as? TelegramSecretChat { + contentType = .secret + } else if let group = peer as? TelegramGroup, case .creator = group.role { + contentType = .group + } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) { + contentType = .group + } else if let _ = interfaceState.peerNearbyData { + contentType = .peerNearby + } else if let peer = peer as? TelegramUser { + if peer.isDeleted || peer.botInfo != nil || peer.flags.contains(.isSupport) || peer.isScam || interfaceState.peerIsBlocked { + contentType = .regular + } else if case .clearedHistory = emptyType { + contentType = .regular + } else { + contentType = .greeting + } + } else { + contentType = .regular } } else { contentType = .regular } - } else { - contentType = .regular } - + var updateGreetingSticker = false var contentTransition = transition if self.content?.0 != contentType { @@ -1044,7 +1057,7 @@ final class ChatEmptyNode: ASDisplayNode { var contentSize = CGSize() if let contentNode = self.content?.1 { - contentSize = contentNode.updateLayout(interfaceState: interfaceState, size: displayRect.size, transition: contentTransition) + contentSize = contentNode.updateLayout(interfaceState: interfaceState, subject: subject, size: displayRect.size, transition: contentTransition) if updateGreetingSticker { self.context.prefetchManager?.prepareNextGreetingSticker() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index bc975f1429..fe7f59fcae 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -257,6 +257,18 @@ private enum PeerInfoScreenInputData: Equatable { case group(groupId: PeerId) } +public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal { + let chatLocationContextHolder = Atomic(value: nil) + return peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder) + |> map { panes -> Bool in + if let panes { + return !panes.isEmpty + } else { + return false + } + } +} + private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal<[PeerInfoPaneKey]?, NoError> { let tags: [(MessageTags, PeerInfoPaneKey)] = [ (.photoOrVideo, .media), diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 080097b6e4..45a1f1e678 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -15,6 +15,46 @@ import AppBundle import DatePickerNode import DebugSettingsUI import TabBarUI +import WallpaperBackgroundNode +import ChatPresentationInterfaceState + +private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { + private var presentationData: PresentationData + private var presentationInterfaceState: ChatPresentationInterfaceState + + let wallpaperBackgroundNode: WallpaperBackgroundNode + let emptyNode: ChatEmptyNode + + init(context: AccountContext) { + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) + + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: true) + self.emptyNode = ChatEmptyNode(context: context, interaction: nil) + + super.init() + + self.addSubnode(self.wallpaperBackgroundNode) + self.addSubnode(self.emptyNode) + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(previewing: false), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil) + + self.wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper) + } + + func updateLayout(size: CGSize, needsTiling: Bool, transition: ContainedViewLayoutTransition) { + let contentBounds = CGRect(origin: .zero, size: size) + self.wallpaperBackgroundNode.updateLayout(size: size, displayMode: needsTiling ? .aspectFit : .aspectFill, transition: transition) + transition.updateFrame(node: self.wallpaperBackgroundNode, frame: contentBounds) + + self.emptyNode.updateLayout(interfaceState: self.presentationInterfaceState, subject: .detailsPlaceholder, loadingNode: nil, backgroundNode: self.wallpaperBackgroundNode, size: contentBounds.size, insets: .zero, transition: transition) + transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: .zero, size: size)) + self.emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) + } +} public final class TelegramRootController: NavigationController { private let context: AccountContext @@ -30,6 +70,8 @@ public final class TelegramRootController: NavigationController { private var presentationDataDisposable: Disposable? private var presentationData: PresentationData + private var detailsPlaceholderNode: DetailsChatPlaceholderNode? + private var applicationInFocusDisposable: Disposable? public init(context: AccountContext) { @@ -37,33 +79,13 @@ public final class TelegramRootController: NavigationController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode? - switch presentationData.chatWallpaper { - case .color: - let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2)) - navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil - default: - let image = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) - navigationDetailsBackgroundMode = image != nil ? .wallpaper(image!) : nil - } - - super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme), backgroundDetailsMode: navigationDetailsBackgroundMode) + super.init(mode: .automaticMasterDetail, theme: NavigationControllerTheme(presentationTheme: self.presentationData.theme)) self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { - if presentationData.chatWallpaper != strongSelf.presentationData.chatWallpaper { - let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode? - switch presentationData.chatWallpaper { - case .color: - let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/EmptyMasterDetailIcon"), color: presentationData.theme.chatList.messageTextColor.withAlphaComponent(0.2)) - navigationDetailsBackgroundMode = image != nil ? .image(image!) : nil - default: - navigationDetailsBackgroundMode = chatControllerBackgroundImage(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper, mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, knockoutMode: strongSelf.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper).flatMap(NavigationEmptyDetailsBackgoundMode.wallpaper) - } - strongSelf.updateBackgroundDetailsMode(navigationDetailsBackgroundMode, transition: .immediate) - } - + strongSelf.detailsPlaceholderNode?.updatePresentationData(presentationData) + let previousTheme = strongSelf.presentationData.theme strongSelf.presentationData = presentationData if previousTheme !== presentationData.theme { @@ -92,6 +114,32 @@ public final class TelegramRootController: NavigationController { self.applicationInFocusDisposable?.dispose() } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let needsRootWallpaperBackgroundNode: Bool + if case .regular = layout.metrics.widthClass { + needsRootWallpaperBackgroundNode = true + } else { + needsRootWallpaperBackgroundNode = false + } + + if needsRootWallpaperBackgroundNode { + let detailsPlaceholderNode: DetailsChatPlaceholderNode + if let current = self.detailsPlaceholderNode { + detailsPlaceholderNode = current + } else { + detailsPlaceholderNode = DetailsChatPlaceholderNode(context: self.context) + detailsPlaceholderNode.wallpaperBackgroundNode.update(wallpaper: self.presentationData.chatWallpaper) + self.detailsPlaceholderNode = detailsPlaceholderNode + } + self.updateDetailsPlaceholderNode(detailsPlaceholderNode) + } else if let _ = self.detailsPlaceholderNode { + self.detailsPlaceholderNode = nil + self.updateDetailsPlaceholderNode(nil) + } + + super.containerLayoutUpdated(layout, transition: transition) + } + public func addRootControllers(showCallsTab: Bool) { let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) tabBarController.navigationPresentation = .master