From ff0e21e4121e3a83e06f1ee97ecd7b1dee58a115 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 24 Jun 2023 13:43:57 +0300 Subject: [PATCH] Tooltip --- .../Sources/ChatListController.swift | 85 ++++++++++++++++--- .../Sources/ChatListControllerNode.swift | 4 +- .../TelegramNotices/Sources/Notices.swift | 25 ++++++ .../Sources/StoryPeerListComponent.swift | 4 + 4 files changed, 104 insertions(+), 14 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 3134c99612..eea9e7d14e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -181,7 +181,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private var rawStorySubscriptions: EngineStorySubscriptions? private var shouldFixStorySubscriptionOrder: Bool = false private var fixedStorySubscriptionOrder: [EnginePeer.Id] = [] - var orderedStorySubscriptions: EngineStorySubscriptions? + private(set) var orderedStorySubscriptions: EngineStorySubscriptions? + private var displayedStoriesTooltip: Bool = false private var storyProgressDisposable: Disposable? private var storySubscriptionsDisposable: Disposable? @@ -1853,11 +1854,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - var wasEmpty = true - if let rawStorySubscriptions = self.rawStorySubscriptions, !rawStorySubscriptions.items.isEmpty { - wasEmpty = false - } - self.rawStorySubscriptions = rawStorySubscriptions var items: [EngineStorySubscriptions.Item] = [] if self.shouldFixStorySubscriptionOrder { @@ -1879,8 +1875,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ) self.fixedStorySubscriptionOrder = items.map(\.peer.id) - let isEmpty = rawStorySubscriptions.items.isEmpty - let transition: ContainedViewLayoutTransition if self.didAppear { transition = .animated(duration: 0.4, curve: .spring) @@ -1888,9 +1882,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController transition = .immediate } - let _ = wasEmpty - let _ = isEmpty - self.chatListDisplayNode.temporaryContentOffsetChangeTransition = transition self.requestLayout(transition: transition) self.chatListDisplayNode.temporaryContentOffsetChangeTransition = nil @@ -1911,6 +1902,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } self.storiesReady.set(.single(true)) + + Queue.mainQueue().after(1.0, { [weak self] in + guard let self else { + return + } + self.maybeDisplayStoryTooltip() + }) }) self.storyProgressDisposable = (self.context.engine.messages.allStoriesUploadProgress() |> deliverOnMainQueue).start(next: { [weak self] progress in @@ -1922,6 +1920,67 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } + fileprivate func maybeDisplayStoryTooltip() { + let content = self.updateHeaderContent() + if content.secondaryContent != nil { + return + } + guard let chatListTitle = content.primaryContent?.chatListTitle else { + return + } + if chatListTitle.activity { + return + } + if self.displayedStoriesTooltip { + return + } + + if let orderedStorySubscriptions = self.orderedStorySubscriptions, !orderedStorySubscriptions.items.isEmpty { + let _ = (ApplicationSpecificNotice.displayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] didDisplay in + guard let self else { + return + } + if didDisplay { + return + } + + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, !navigationBarView.storiesUnlocked, !self.displayedStoriesTooltip { + if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView(), let (anchorView, anchorRect) = storyPeerListView.anchorForTooltip() { + self.displayedStoriesTooltip = true + + let absoluteFrame = anchorView.convert(anchorRect, to: self.view) + //TODO:localize + + let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle) + var itemListString: String = itemList.joined(separator: ", ") + if #available(iOS 13.0, *) { + let listFormatter = ListFormatter() + listFormatter.locale = localeWithStrings(self.presentationData.strings) + if let value = listFormatter.string(from: itemList) { + itemListString = value + } + } + + let text: String = "Tap above to view updates\nfrom \(itemListString)" + + let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0)) + self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + guard let self else { + return nil + } + return (self.displayNode, absoluteFrame.insetBy(dx: 0.0, dy: 0.0).offsetBy(dx: 0.0, dy: 0.0)) + })) + + #if !DEBUG + let _ = ApplicationSpecificNotice.setDisplayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager).start() + #endif + } + } + }) + } + } + public override func displayNodeDidLoad() { super.displayNodeDidLoad() @@ -2351,7 +2410,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - func updateHeaderContent(layout: ContainerViewLayout) -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) { + func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) { var primaryContent: ChatListHeaderComponent.Content? if let primaryContext = self.primaryContext { var backTitle: String? @@ -5684,6 +5743,8 @@ private final class ChatListLocationContext { } self.parentController?.requestLayout(transition: .animated(duration: 0.45, curve: .spring)) + + self.parentController?.maybeDisplayStoryTooltip() } private func updateForum( diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 3e3d9c0291..3b71c025ae 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1915,7 +1915,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: Transition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { - let headerContent = self.controller?.updateHeaderContent(layout: layout) + let headerContent = self.controller?.updateHeaderContent() var tabsNode: ASDisplayNode? var tabsNodeIsSearch = false @@ -2355,7 +2355,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } private func shouldStopScrolling(listView: ListView, velocity: CGFloat, isPrimary: Bool) -> Bool { - if abs(velocity) > 1.0 { + if abs(velocity) > 0.8 { return false } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index aef9b83c44..b7fd91b2fa 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -173,6 +173,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case chatWallpaperLightPreviewTip = 39 case chatWallpaperDarkPreviewTip = 40 case displayChatListContacts = 41 + case displayChatListStoriesTooltip = 42 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -394,6 +395,10 @@ private struct ApplicationSpecificNoticeKeys { static func displayChatListContacts() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key) } + + static func displayChatListStoriesTooltip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListStoriesTooltip.key) + } } public struct ApplicationSpecificNotice { @@ -1445,6 +1450,26 @@ public struct ApplicationSpecificNotice { |> ignoreValues } + public static func displayChatListStoriesTooltip(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayChatListStoriesTooltip()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + } + + public static func setDisplayChatListStoriesTooltip(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListStoriesTooltip(), entry) + } + } + |> ignoreValues + } + public static func reset(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 59e34ca326..45c84e0515 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -323,6 +323,10 @@ public final class StoryPeerListComponent: Component { }) } + public func anchorForTooltip() -> (UIView, CGRect)? { + return (self.collapsedButton, self.collapsedButton.bounds) + } + public func transitionViewForItem(peerId: EnginePeer.Id) -> (UIView, StoryContainerScreen.TransitionView)? { if self.collapsedButton.isUserInteractionEnabled { return nil