mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Various improvements
This commit is contained in:
parent
df249f5302
commit
04b25d7152
@ -11127,6 +11127,9 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.BottomSearchPanel.MessageCount_1" = "message";
|
"Chat.BottomSearchPanel.MessageCount_1" = "message";
|
||||||
"Chat.BottomSearchPanel.MessageCount_any" = "messages";
|
"Chat.BottomSearchPanel.MessageCount_any" = "messages";
|
||||||
|
|
||||||
|
"Chat.BottomSearchPanel.StoryCount_1" = "story";
|
||||||
|
"Chat.BottomSearchPanel.StoryCount_any" = "stories";
|
||||||
|
|
||||||
"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@";
|
"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@";
|
||||||
"Chat.BottomSearchPanel.DisplayModeChat" = "Chat";
|
"Chat.BottomSearchPanel.DisplayModeChat" = "Chat";
|
||||||
"Chat.BottomSearchPanel.DisplayModeList" = "List";
|
"Chat.BottomSearchPanel.DisplayModeList" = "List";
|
||||||
@ -12397,6 +12400,12 @@ Sorry for the inconvenience.";
|
|||||||
"HashtagSearch.Stories_any" = "%@ Stories";
|
"HashtagSearch.Stories_any" = "%@ Stories";
|
||||||
"HashtagSearch.LocalStoriesFound" = "%1$@ in %2$@";
|
"HashtagSearch.LocalStoriesFound" = "%1$@ in %2$@";
|
||||||
|
|
||||||
|
"HashtagSearch.Posts_1" = "%@ Message";
|
||||||
|
"HashtagSearch.Posts_any" = "%@ Messages";
|
||||||
|
"HashtagSearch.FoundInfoFormat" = "View %1$@ with %2$@";
|
||||||
|
"HashtagSearch.FoundStories" = "stories";
|
||||||
|
"HashtagSearch.FoundPosts" = "posts";
|
||||||
|
|
||||||
"Stars.BotRevenue.Title" = "Stars Balance";
|
"Stars.BotRevenue.Title" = "Stars Balance";
|
||||||
"Stars.BotRevenue.Revenue.Title" = "Stars Received";
|
"Stars.BotRevenue.Revenue.Title" = "Stars Received";
|
||||||
"Stars.BotRevenue.Proceeds.Title" = "Rewards Overview";
|
"Stars.BotRevenue.Proceeds.Title" = "Rewards Overview";
|
||||||
|
@ -941,7 +941,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
func makeStorageManagementController(context: AccountContext) -> ViewController
|
func makeStorageManagementController(context: AccountContext) -> ViewController
|
||||||
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
|
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
|
||||||
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
|
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
|
||||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
|
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController
|
||||||
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController
|
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController
|
||||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||||
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
||||||
|
@ -1031,14 +1031,18 @@ public protocol ChatController: ViewController {
|
|||||||
|
|
||||||
var visibleContextController: ViewController? { get }
|
var visibleContextController: ViewController? { get }
|
||||||
|
|
||||||
|
var contentContainerNode: ASDisplayNode { get }
|
||||||
|
|
||||||
var searching: ValuePromise<Bool> { get }
|
var searching: ValuePromise<Bool> { get }
|
||||||
|
var searchResultsCount: ValuePromise<Int32> { get }
|
||||||
|
var externalSearchResultsCount: Int32? { get set }
|
||||||
|
|
||||||
var alwaysShowSearchResultsAsList: Bool { get set }
|
var alwaysShowSearchResultsAsList: Bool { get set }
|
||||||
var includeSavedPeersInSearchResults: Bool { get set }
|
var includeSavedPeersInSearchResults: Bool { get set }
|
||||||
var showListEmptyResults: Bool { get set }
|
var showListEmptyResults: Bool { get set }
|
||||||
|
func beginMessageSearch(_ query: String)
|
||||||
|
|
||||||
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
||||||
func beginMessageSearch(_ query: String)
|
|
||||||
func displayPromoAnnouncement(text: String)
|
func displayPromoAnnouncement(text: String)
|
||||||
|
|
||||||
func updatePushedTransition(_ fraction: CGFloat, transition: ContainedViewLayoutTransition)
|
func updatePushedTransition(_ fraction: CGFloat, transition: ContainedViewLayoutTransition)
|
||||||
|
@ -253,6 +253,13 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||||
var updatedItems: [ContextMenuItem] = []
|
var updatedItems: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, iconPosition: .left, action: { c, _ in
|
||||||
|
c?.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true)
|
||||||
|
})))
|
||||||
|
updatedItems.append(.separator)
|
||||||
|
|
||||||
for filter in filters {
|
for filter in filters {
|
||||||
if case let .filter(_, title, _, data) = filter {
|
if case let .filter(_, title, _, data) = filter {
|
||||||
let predicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId)
|
let predicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId)
|
||||||
@ -338,16 +345,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedItems.append(.separator)
|
|
||||||
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, iconPosition: .left, action: { c, _ in
|
|
||||||
c?.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true)
|
|
||||||
})))
|
|
||||||
|
|
||||||
c?.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true)
|
c?.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true)
|
||||||
})))
|
})))
|
||||||
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,10 @@ swift_library(
|
|||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/Components/BundleIconComponent",
|
"//submodules/Components/BundleIconComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
|
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode",
|
||||||
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
|
"//submodules/UIKitRuntimeUtils",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -25,6 +25,8 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
private let query: String
|
private let query: String
|
||||||
let mode: Mode
|
let mode: Mode
|
||||||
let publicPosts: Bool
|
let publicPosts: Bool
|
||||||
|
let stories: Bool
|
||||||
|
let forceDark: Bool
|
||||||
|
|
||||||
private var transitionDisposable: Disposable?
|
private var transitionDisposable: Disposable?
|
||||||
private let openMessageFromSearchDisposable = MetaDisposable()
|
private let openMessageFromSearchDisposable = MetaDisposable()
|
||||||
@ -39,17 +41,23 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
return self.displayNode as! HashtagSearchControllerNode
|
return self.displayNode as! HashtagSearchControllerNode
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, peer: EnginePeer?, query: String, mode: Mode = .generic, publicPosts: Bool = false) {
|
public init(context: AccountContext, peer: EnginePeer?, query: String, mode: Mode = .generic, publicPosts: Bool = false, stories: Bool = false, forceDark: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.query = query
|
self.query = query
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.publicPosts = publicPosts
|
self.publicPosts = publicPosts
|
||||||
|
self.stories = stories
|
||||||
|
self.forceDark = forceDark
|
||||||
|
|
||||||
self.animationCache = context.animationCache
|
self.animationCache = context.animationCache
|
||||||
self.animationRenderer = context.animationRenderer
|
self.animationRenderer = context.animationRenderer
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
if forceDark {
|
||||||
|
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
}
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
|
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
|
||||||
|
|
||||||
@ -69,6 +77,11 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
let previousTheme = self.presentationData.theme
|
let previousTheme = self.presentationData.theme
|
||||||
let previousStrings = self.presentationData.strings
|
let previousStrings = self.presentationData.strings
|
||||||
|
|
||||||
|
var presentationData = presentationData
|
||||||
|
if forceDark {
|
||||||
|
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
}
|
||||||
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||||
|
@ -9,6 +9,8 @@ import AccountContext
|
|||||||
import ChatListUI
|
import ChatListUI
|
||||||
import SegmentedControlNode
|
import SegmentedControlNode
|
||||||
import ChatListSearchItemHeader
|
import ChatListSearchItemHeader
|
||||||
|
import PeerInfoVisualMediaPaneNode
|
||||||
|
import UIKitRuntimeUtils
|
||||||
|
|
||||||
final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -30,6 +32,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
private let isSearching = Promise<Bool>()
|
private let isSearching = Promise<Bool>()
|
||||||
private var isSearchingDisposable: Disposable?
|
private var isSearchingDisposable: Disposable?
|
||||||
|
|
||||||
|
private var searchResultsCount: Int32 = 0
|
||||||
|
private var searchResultsCountDisposable: Disposable?
|
||||||
|
|
||||||
private let clippingNode: ASDisplayNode
|
private let clippingNode: ASDisplayNode
|
||||||
private let containerNode: ASDisplayNode
|
private let containerNode: ASDisplayNode
|
||||||
let currentController: ChatController?
|
let currentController: ChatController?
|
||||||
@ -39,10 +44,13 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
let globalController: ChatController?
|
let globalController: ChatController?
|
||||||
let globalChatContents: HashtagSearchGlobalChatContents?
|
let globalChatContents: HashtagSearchGlobalChatContents?
|
||||||
|
|
||||||
private var globalStorySearchContext: SearchStoryListContext?
|
private var storySearchContext: SearchStoryListContext?
|
||||||
private var globalStorySearchDisposable = MetaDisposable()
|
private var storySearchDisposable = MetaDisposable()
|
||||||
private var globalStorySearchState: StoryListContext.State?
|
private var storySearchState: StoryListContext.State?
|
||||||
private var globalStorySearchComponentView: ComponentView<Empty>?
|
private var storySearchComponentView: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var storySearchPaneNode: PeerInfoStoryPaneNode?
|
||||||
|
private var isDisplayingStories = false
|
||||||
|
|
||||||
private var panRecognizer: InteractiveTransitionGestureRecognizer?
|
private var panRecognizer: InteractiveTransitionGestureRecognizer?
|
||||||
|
|
||||||
@ -57,8 +65,14 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
self.isCashtag = query.hasPrefix("$")
|
self.isCashtag = query.hasPrefix("$")
|
||||||
self.presentationData = controller.presentationData
|
self.presentationData = controller.presentationData
|
||||||
|
self.isDisplayingStories = controller.stories
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
var controllerParams: ChatControllerParams?
|
||||||
|
if controller.forceDark {
|
||||||
|
controllerParams = ChatControllerParams(forcedTheme: defaultDarkColorPresentationTheme, forcedWallpaper: defaultBuiltinWallpaper(data: .default, colors: defaultDarkWallpaperGradientColors.map(\.rgb), intensity: -34))
|
||||||
|
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
}
|
||||||
|
|
||||||
self.clippingNode = ASDisplayNode()
|
self.clippingNode = ASDisplayNode()
|
||||||
self.clippingNode.clipsToBounds = true
|
self.clippingNode.clipsToBounds = true
|
||||||
@ -78,7 +92,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
|
|
||||||
let navigationController = controller.navigationController as? NavigationController
|
let navigationController = controller.navigationController as? NavigationController
|
||||||
if let peer, controller.mode != .noChat {
|
if let peer, controller.mode != .noChat {
|
||||||
self.currentController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController), params: nil)
|
self.currentController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController), params: controllerParams)
|
||||||
self.currentController?.alwaysShowSearchResultsAsList = true
|
self.currentController?.alwaysShowSearchResultsAsList = true
|
||||||
self.currentController?.showListEmptyResults = true
|
self.currentController?.showListEmptyResults = true
|
||||||
self.currentController?.customNavigationController = navigationController
|
self.currentController?.customNavigationController = navigationController
|
||||||
@ -89,7 +103,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
if let _ = peer, controller.mode != .chatOnly {
|
if let _ = peer, controller.mode != .chatOnly {
|
||||||
let myChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: false)
|
let myChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: false)
|
||||||
self.myChatContents = myChatContents
|
self.myChatContents = myChatContents
|
||||||
self.myController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: myChatContents), botStart: nil, mode: .standard(.default), params: nil)
|
self.myController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: myChatContents), botStart: nil, mode: .standard(.default), params: controllerParams)
|
||||||
self.myController?.alwaysShowSearchResultsAsList = true
|
self.myController?.alwaysShowSearchResultsAsList = true
|
||||||
self.myController?.showListEmptyResults = true
|
self.myController?.showListEmptyResults = true
|
||||||
self.myController?.customNavigationController = navigationController
|
self.myController?.customNavigationController = navigationController
|
||||||
@ -100,7 +114,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
|
|
||||||
let globalChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: true)
|
let globalChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: true)
|
||||||
self.globalChatContents = globalChatContents
|
self.globalChatContents = globalChatContents
|
||||||
self.globalController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: globalChatContents), botStart: nil, mode: .standard(.default), params: nil)
|
self.globalController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: globalChatContents), botStart: nil, mode: .standard(.default), params: controllerParams)
|
||||||
self.globalController?.alwaysShowSearchResultsAsList = true
|
self.globalController?.alwaysShowSearchResultsAsList = true
|
||||||
self.globalController?.showListEmptyResults = true
|
self.globalController?.showListEmptyResults = true
|
||||||
self.globalController?.customNavigationController = navigationController
|
self.globalController?.customNavigationController = navigationController
|
||||||
@ -182,7 +196,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addSubnode(self.shimmerNode)
|
if !self.isDisplayingStories {
|
||||||
|
self.addSubnode(self.shimmerNode)
|
||||||
|
}
|
||||||
|
|
||||||
self.searchContentNode.setQueryUpdated { [weak self] query in
|
self.searchContentNode.setQueryUpdated { [weak self] query in
|
||||||
self?.searchQueryPromise.set(query)
|
self?.searchQueryPromise.set(query)
|
||||||
@ -227,13 +243,25 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if let currentController = self.currentController {
|
||||||
|
self.searchResultsCountDisposable = (currentController.searchResultsCount.get()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] searchResultsCount in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.searchResultsCount = searchResultsCount
|
||||||
|
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
self.updateStorySearch()
|
self.updateStorySearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.searchQueryDisposable?.dispose()
|
self.searchQueryDisposable?.dispose()
|
||||||
self.isSearchingDisposable?.dispose()
|
self.isSearchingDisposable?.dispose()
|
||||||
self.globalStorySearchDisposable.dispose()
|
self.searchResultsCountDisposable?.dispose()
|
||||||
|
self.storySearchDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var panAllowedDirections: InteractiveTransitionGestureRecognizerDirections {
|
private var panAllowedDirections: InteractiveTransitionGestureRecognizerDirections {
|
||||||
@ -373,29 +401,30 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateStorySearch() {
|
private func updateStorySearch() {
|
||||||
self.globalStorySearchState = nil
|
self.storySearchState = nil
|
||||||
self.globalStorySearchDisposable.set(nil)
|
self.storySearchDisposable.set(nil)
|
||||||
self.globalStorySearchContext = nil
|
self.storySearchContext = nil
|
||||||
|
|
||||||
if !self.query.isEmpty {
|
if !self.query.isEmpty {
|
||||||
var peerId: EnginePeer.Id?
|
var peerId: EnginePeer.Id?
|
||||||
if self.controller?.mode == .chatOnly {
|
if self.controller?.mode == .chatOnly {
|
||||||
peerId = self.peer?.id
|
peerId = self.peer?.id
|
||||||
}
|
}
|
||||||
let globalStorySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(peerId, self.query))
|
let storySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(peerId, self.query))
|
||||||
self.globalStorySearchDisposable.set((globalStorySearchContext.state
|
self.storySearchDisposable.set((storySearchContext.state
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if state.totalCount > 0 {
|
if state.totalCount > 0 {
|
||||||
self.globalStorySearchState = state
|
self.storySearchState = state
|
||||||
} else {
|
} else {
|
||||||
self.globalStorySearchState = nil
|
self.storySearchState = nil
|
||||||
|
self.currentController?.externalSearchResultsCount = nil
|
||||||
}
|
}
|
||||||
self.requestUpdate(transition: .animated(duration: 0.25, curve: .easeInOut))
|
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||||
}))
|
}))
|
||||||
self.globalStorySearchContext = globalStorySearchContext
|
self.storySearchContext = storySearchContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,6 +449,37 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func animateContentOut() {
|
||||||
|
guard let controller = self.currentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.contentContainerNode.layer.animateSublayerScale(from: 1.0, to: 0.95, duration: 0.3, removeOnCompletion: false)
|
||||||
|
|
||||||
|
if let blurFilter = makeBlurFilter() {
|
||||||
|
blurFilter.setValue(30.0 as NSNumber, forKey: "inputRadius")
|
||||||
|
controller.contentContainerNode.layer.filters = [blurFilter]
|
||||||
|
controller.contentContainerNode.layer.animate(from: 0.0 as NSNumber, to: 30.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.3, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animateContentIn() {
|
||||||
|
guard let controller = self.currentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.contentContainerNode.layer.animateSublayerScale(from: 0.95, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
|
if let blurFilter = makeBlurFilter() {
|
||||||
|
blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius")
|
||||||
|
controller.contentContainerNode.layer.filters = [blurFilter]
|
||||||
|
controller.contentContainerNode.layer.animate(from: 30.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak controller] completed in
|
||||||
|
guard let controller, completed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.contentContainerNode.layer.filters = []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func requestUpdate(transition: ContainedViewLayoutTransition) {
|
func requestUpdate(transition: ContainedViewLayoutTransition) {
|
||||||
if let (layout, navigationHeight) = self.containerLayout {
|
if let (layout, navigationHeight) = self.containerLayout {
|
||||||
let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
|
let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
|
||||||
@ -440,45 +500,69 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
self.insertSubnode(self.clippingNode, at: 0)
|
self.insertSubnode(self.clippingNode, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var storyParentController: ViewController?
|
||||||
|
if self.controller?.mode == .chatOnly {
|
||||||
|
storyParentController = self.currentController
|
||||||
|
} else {
|
||||||
|
storyParentController = self.globalController
|
||||||
|
}
|
||||||
|
|
||||||
var currentTopInset: CGFloat = 0.0
|
var currentTopInset: CGFloat = 0.0
|
||||||
var globalTopInset: CGFloat = 0.0
|
var globalTopInset: CGFloat = 0.0
|
||||||
if let state = self.globalStorySearchState {
|
|
||||||
var parentController: ViewController?
|
var panelSearchState: StoryResultsPanelComponent.SearchState?
|
||||||
if self.controller?.mode == .chatOnly {
|
if let storySearchState = self.storySearchState {
|
||||||
parentController = self.currentController
|
if self.isDisplayingStories {
|
||||||
|
if self.searchResultsCount > 0 {
|
||||||
|
panelSearchState = .messages(self.searchResultsCount)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
parentController = self.globalController
|
panelSearchState = .stories(storySearchState)
|
||||||
}
|
}
|
||||||
if let parentController {
|
}
|
||||||
|
|
||||||
|
if self.isDisplayingStories {
|
||||||
|
if let storySearchState = self.storySearchState {
|
||||||
|
self.currentController?.externalSearchResultsCount = Int32(storySearchState.totalCount)
|
||||||
|
} else {
|
||||||
|
self.currentController?.externalSearchResultsCount = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.currentController?.externalSearchResultsCount = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let panelSearchState {
|
||||||
|
if let storyParentController {
|
||||||
let componentView: ComponentView<Empty>
|
let componentView: ComponentView<Empty>
|
||||||
var panelTransition = ComponentTransition(transition)
|
var panelTransition = ComponentTransition(transition)
|
||||||
if let current = self.globalStorySearchComponentView {
|
if let current = self.storySearchComponentView {
|
||||||
componentView = current
|
componentView = current
|
||||||
} else {
|
} else {
|
||||||
panelTransition = .immediate
|
panelTransition = .immediate
|
||||||
componentView = ComponentView()
|
componentView = ComponentView()
|
||||||
self.globalStorySearchComponentView = componentView
|
self.storySearchComponentView = componentView
|
||||||
}
|
}
|
||||||
let panelSize = componentView.update(
|
let panelSize = componentView.update(
|
||||||
transition: .immediate,
|
transition: panelTransition,
|
||||||
component: AnyComponent(StoryResultsPanelComponent(
|
component: AnyComponent(StoryResultsPanelComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
strings: self.presentationData.strings,
|
strings: self.presentationData.strings,
|
||||||
query: self.query,
|
query: self.query,
|
||||||
peer: self.controller?.mode == .chatOnly ? self.peer : nil,
|
peer: self.controller?.mode == .chatOnly ? self.peer : nil,
|
||||||
state: state,
|
state: panelSearchState,
|
||||||
sideInset: layout.safeInsets.left,
|
sideInset: layout.safeInsets.left,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var peer: EnginePeer?
|
|
||||||
if self.controller?.mode == .chatOnly {
|
if self.controller?.mode == .chatOnly {
|
||||||
peer = self.peer
|
self.isDisplayingStories = !self.isDisplayingStories
|
||||||
|
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
} else {
|
||||||
|
let searchController = self.context.sharedContext.makeStorySearchController(context: self.context, scope: .query(nil, self.query), listContext: self.storySearchContext)
|
||||||
|
self.controller?.push(searchController)
|
||||||
}
|
}
|
||||||
let searchController = self.context.sharedContext.makeStorySearchController(context: self.context, scope: .query(peer, self.query), listContext: self.globalStorySearchContext)
|
|
||||||
self.controller?.push(searchController)
|
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -487,8 +571,8 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top - 36.0), size: panelSize)
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top - 36.0), size: panelSize)
|
||||||
if let view = componentView.view {
|
if let view = componentView.view {
|
||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
parentController.view.addSubview(view)
|
storyParentController.view.addSubview(view)
|
||||||
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -panelSize.height), to: .zero, duration: 0.25, additive: true)
|
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -panelSize.height), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
}
|
}
|
||||||
panelTransition.setFrame(view: view, frame: panelFrame)
|
panelTransition.setFrame(view: view, frame: panelFrame)
|
||||||
}
|
}
|
||||||
@ -498,9 +582,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
globalTopInset += panelSize.height
|
globalTopInset += panelSize.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let globalStorySearchComponentView = self.globalStorySearchComponentView {
|
} else if let storySearchComponentView = self.storySearchComponentView {
|
||||||
globalStorySearchComponentView.view?.removeFromSuperview()
|
storySearchComponentView.view?.removeFromSuperview()
|
||||||
self.globalStorySearchComponentView = nil
|
self.storySearchComponentView = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if let controller = self.currentController {
|
if let controller = self.currentController {
|
||||||
@ -548,6 +632,75 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.isDisplayingStories, let peer = self.peer, let storySearchContext = self.storySearchContext {
|
||||||
|
let storySearchPaneNode: PeerInfoStoryPaneNode
|
||||||
|
var paneTransition = transition
|
||||||
|
if let current = self.storySearchPaneNode {
|
||||||
|
storySearchPaneNode = current
|
||||||
|
} else {
|
||||||
|
storySearchPaneNode = PeerInfoStoryPaneNode(
|
||||||
|
context: self.context,
|
||||||
|
scope: .search(peerId: peer.id, query: self.query),
|
||||||
|
captureProtected: false,
|
||||||
|
isProfileEmbedded: false,
|
||||||
|
canManageStories: false,
|
||||||
|
navigationController: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.controller?.navigationController as? NavigationController
|
||||||
|
},
|
||||||
|
listContext: storySearchContext
|
||||||
|
)
|
||||||
|
self.storySearchPaneNode = storySearchPaneNode
|
||||||
|
if let storySearchView = self.storySearchComponentView?.view {
|
||||||
|
storySearchView.superview?.insertSubview(storySearchPaneNode.view, belowSubview: storySearchView)
|
||||||
|
} else {
|
||||||
|
storyParentController?.view.addSubview(storySearchPaneNode.view)
|
||||||
|
}
|
||||||
|
paneTransition = .immediate
|
||||||
|
|
||||||
|
if transition.isAnimated {
|
||||||
|
storySearchPaneNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
storySearchPaneNode.layer.animateSublayerScale(from: 0.95, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.animateContentOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bottomInset: CGFloat = 0.0
|
||||||
|
if case .regular = layout.metrics.widthClass {
|
||||||
|
bottomInset += 49.0
|
||||||
|
} else {
|
||||||
|
bottomInset += 45.0
|
||||||
|
}
|
||||||
|
bottomInset += layout.intrinsicInsets.bottom
|
||||||
|
|
||||||
|
storySearchPaneNode.update(
|
||||||
|
size: layout.size,
|
||||||
|
topInset: navigationBarHeight,
|
||||||
|
sideInset: layout.safeInsets.left,
|
||||||
|
bottomInset: 0.0,
|
||||||
|
deviceMetrics: layout.deviceMetrics,
|
||||||
|
visibleHeight: layout.size.height - currentTopInset,
|
||||||
|
isScrollingLockedAtTop: false,
|
||||||
|
expandProgress: 1.0,
|
||||||
|
navigationHeight: 0.0,
|
||||||
|
presentationData: self.presentationData,
|
||||||
|
synchronous: false,
|
||||||
|
transition: paneTransition
|
||||||
|
)
|
||||||
|
storySearchPaneNode.view.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
|
paneTransition.updateFrame(node: storySearchPaneNode, frame: CGRect(origin: CGPoint(x: 0.0, y: currentTopInset), size: CGSize(width: layout.size.width, height: layout.size.height - bottomInset - currentTopInset)))
|
||||||
|
} else if let storySearchPaneNode = self.storySearchPaneNode {
|
||||||
|
self.storySearchPaneNode = nil
|
||||||
|
|
||||||
|
storySearchPaneNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
storySearchPaneNode.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
storySearchPaneNode.layer.animateSublayerScale(from: 1.0, to: 0.95, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.animateContentIn()
|
||||||
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: .zero, size: layout.size))
|
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: .zero, size: layout.size))
|
||||||
|
|
||||||
let containerPosition: CGFloat = -layout.size.width * CGFloat(self.searchContentNode.selectedIndex) - self.panTransitionFraction * layout.size.width
|
let containerPosition: CGFloat = -layout.size.width * CGFloat(self.searchContentNode.selectedIndex) - self.panTransitionFraction * layout.size.width
|
||||||
@ -559,7 +712,11 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
|||||||
self.shimmerNode.update(context: self.context, size: CGSize(width: layout.size.width - overflowInset * 2.0, height: layout.size.height), presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, key: .chats, hasSelection: false, transition: transition)
|
self.shimmerNode.update(context: self.context, size: CGSize(width: layout.size.width - overflowInset * 2.0, height: layout.size.height), presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, key: .chats, hasSelection: false, transition: transition)
|
||||||
|
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
self.insertSubnode(self.recentListNode, aboveSubnode: self.shimmerNode)
|
if self.shimmerNode.supernode != nil {
|
||||||
|
self.insertSubnode(self.recentListNode, aboveSubnode: self.shimmerNode)
|
||||||
|
} else {
|
||||||
|
self.insertSubnode(self.recentListNode, aboveSubnode: self.clippingNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.recentListNode, frame: CGRect(origin: .zero, size: layout.size))
|
transition.updateFrame(node: self.recentListNode, frame: CGRect(origin: .zero, size: layout.size))
|
||||||
|
@ -7,14 +7,20 @@ import MultilineTextComponent
|
|||||||
import BundleIconComponent
|
import BundleIconComponent
|
||||||
import StorySetIndicatorComponent
|
import StorySetIndicatorComponent
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import AnimatedTextComponent
|
||||||
|
import BlurredBackgroundComponent
|
||||||
|
|
||||||
final class StoryResultsPanelComponent: CombinedComponent {
|
final class StoryResultsPanelComponent: CombinedComponent {
|
||||||
|
enum SearchState: Equatable {
|
||||||
|
case stories(StoryListContext.State)
|
||||||
|
case messages(Int32)
|
||||||
|
}
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let query: String
|
let query: String
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let state: StoryListContext.State
|
let state: SearchState
|
||||||
let sideInset: CGFloat
|
let sideInset: CGFloat
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
@ -24,7 +30,7 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
query: String,
|
query: String,
|
||||||
peer: EnginePeer?,
|
peer: EnginePeer?,
|
||||||
state: StoryListContext.State,
|
state: SearchState,
|
||||||
sideInset: CGFloat,
|
sideInset: CGFloat,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
@ -61,10 +67,11 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
let background = Child(Rectangle.self)
|
let background = Child(BlurredBackgroundComponent.self)
|
||||||
let avatars = Child(StorySetIndicatorComponent.self)
|
let avatars = Child(StorySetIndicatorComponent.self)
|
||||||
|
let titlePrefix = Child(AnimatedTextComponent.self)
|
||||||
let title = Child(MultilineTextComponent.self)
|
let title = Child(MultilineTextComponent.self)
|
||||||
let text = Child(MultilineTextComponent.self)
|
let text = Child(AnimatedTextComponent.self)
|
||||||
let arrow = Child(BundleIconComponent.self)
|
let arrow = Child(BundleIconComponent.self)
|
||||||
let separator = Child(Rectangle.self)
|
let separator = Child(Rectangle.self)
|
||||||
let button = Child(Button.self)
|
let button = Child(Button.self)
|
||||||
@ -74,39 +81,47 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
|
|
||||||
let spacing: CGFloat = 3.0
|
let spacing: CGFloat = 3.0
|
||||||
|
|
||||||
let textLeftInset: CGFloat = 81.0 + component.sideInset
|
var textLeftInset: CGFloat = 16.0 + component.sideInset
|
||||||
let textTopInset: CGFloat = 9.0
|
let textTopInset: CGFloat = 9.0
|
||||||
|
|
||||||
var existingPeerIds = Set<EnginePeer.Id>()
|
var existingPeerIds = Set<EnginePeer.Id>()
|
||||||
var items: [StorySetIndicatorComponent.Item] = []
|
var items: [StorySetIndicatorComponent.Item] = []
|
||||||
for item in component.state.items {
|
switch component.state {
|
||||||
guard let peer = item.peer, !existingPeerIds.contains(peer.id) else {
|
case let .stories(state):
|
||||||
continue
|
for item in state.items {
|
||||||
|
guard let peer = item.peer, !existingPeerIds.contains(peer.id) || component.peer != nil else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingPeerIds.insert(peer.id)
|
||||||
|
items.append(StorySetIndicatorComponent.Item(storyItem: item.storyItem, peer: peer))
|
||||||
}
|
}
|
||||||
existingPeerIds.insert(peer.id)
|
textLeftInset += 65.0
|
||||||
items.append(StorySetIndicatorComponent.Item(storyItem: item.storyItem, peer: peer))
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let avatars = avatars.update(
|
var titlePrefixString: [AnimatedTextComponent.Item] = []
|
||||||
component: StorySetIndicatorComponent(
|
|
||||||
context: component.context,
|
|
||||||
strings: component.strings,
|
|
||||||
items: Array(items.prefix(3)),
|
|
||||||
displayAvatars: true,
|
|
||||||
hasUnseen: true,
|
|
||||||
hasUnseenPrivate: false,
|
|
||||||
totalCount: 0,
|
|
||||||
theme: component.theme,
|
|
||||||
action: {}
|
|
||||||
),
|
|
||||||
availableSize: context.availableSize,
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
|
|
||||||
let titleString: NSAttributedString
|
let titleString: NSAttributedString
|
||||||
|
var textString: [AnimatedTextComponent.Item] = []
|
||||||
if let peer = component.peer, let username = peer.addressName {
|
if let peer = component.peer, let username = peer.addressName {
|
||||||
let storiesString = component.strings.HashtagSearch_Stories(Int32(component.state.totalCount))
|
let entityType: String
|
||||||
let fullString = component.strings.HashtagSearch_LocalStoriesFound(storiesString, "@\(username)")
|
switch component.state {
|
||||||
|
case let .messages(count):
|
||||||
|
titlePrefixString = [AnimatedTextComponent.Item(
|
||||||
|
id: "text",
|
||||||
|
isUnbreakable: true,
|
||||||
|
content: .text(component.strings.HashtagSearch_Posts(count))
|
||||||
|
)]
|
||||||
|
entityType = component.strings.HashtagSearch_FoundPosts
|
||||||
|
case let .stories(state):
|
||||||
|
titlePrefixString = [AnimatedTextComponent.Item(
|
||||||
|
id: "text",
|
||||||
|
isUnbreakable: true,
|
||||||
|
content: .text(component.strings.HashtagSearch_Stories(Int32(state.totalCount)))
|
||||||
|
)]
|
||||||
|
entityType = component.strings.HashtagSearch_FoundStories
|
||||||
|
}
|
||||||
|
let fullString = component.strings.HashtagSearch_LocalStoriesFound("", "@\(username)")
|
||||||
titleString = NSMutableAttributedString(
|
titleString = NSMutableAttributedString(
|
||||||
string: fullString.string,
|
string: fullString.string,
|
||||||
font: Font.semibold(15.0),
|
font: Font.semibold(15.0),
|
||||||
@ -116,13 +131,48 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
if let lastRange = fullString.ranges.last?.range {
|
if let lastRange = fullString.ranges.last?.range {
|
||||||
(titleString as? NSMutableAttributedString)?.addAttribute(NSAttributedString.Key.foregroundColor, value: component.theme.rootController.navigationBar.accentTextColor, range: lastRange)
|
(titleString as? NSMutableAttributedString)?.addAttribute(NSAttributedString.Key.foregroundColor, value: component.theme.rootController.navigationBar.accentTextColor, range: lastRange)
|
||||||
}
|
}
|
||||||
|
textString = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.HashtagSearch_FoundInfoFormat(
|
||||||
|
".",
|
||||||
|
"."
|
||||||
|
), id: "info", mapping: [
|
||||||
|
0: .text(entityType),
|
||||||
|
1: .text(component.query)
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
titleString = NSAttributedString(
|
if case let .stories(state) = component.state {
|
||||||
string: component.strings.HashtagSearch_StoriesFound(Int32(component.state.totalCount)),
|
titleString = NSAttributedString(
|
||||||
font: Font.semibold(15.0),
|
string: component.strings.HashtagSearch_StoriesFound(Int32(state.totalCount)),
|
||||||
textColor: component.theme.rootController.navigationBar.primaryTextColor,
|
font: Font.semibold(15.0),
|
||||||
paragraphAlignment: .natural
|
textColor: component.theme.rootController.navigationBar.primaryTextColor,
|
||||||
|
paragraphAlignment: .natural
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
titleString = NSAttributedString()
|
||||||
|
}
|
||||||
|
textString = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.HashtagSearch_FoundInfoFormat(
|
||||||
|
".",
|
||||||
|
"."
|
||||||
|
), id: "info", mapping: [
|
||||||
|
0: .text(component.strings.HashtagSearch_FoundStories),
|
||||||
|
1: .text(component.query)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
var titlePrefixOffset: CGFloat = 0.0
|
||||||
|
var titlePrefixChild: _UpdatedChildComponent?
|
||||||
|
if !titlePrefixString.isEmpty {
|
||||||
|
let titlePrefix = titlePrefix.update(
|
||||||
|
component: AnimatedTextComponent(
|
||||||
|
font: Font.semibold(15.0),
|
||||||
|
color: component.theme.rootController.navigationBar.primaryTextColor,
|
||||||
|
items: titlePrefixString,
|
||||||
|
noDelay: true
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: context.availableSize.width - textLeftInset - 64.0, height: context.availableSize.height),
|
||||||
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
titlePrefixOffset = titlePrefix.size.width + 1.0
|
||||||
|
titlePrefixChild = titlePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
@ -131,23 +181,19 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: context.availableSize.width - textLeftInset, height: CGFloat.greatestFiniteMagnitude),
|
availableSize: CGSize(width: context.availableSize.width - textLeftInset - 64.0 - titlePrefixOffset, height: CGFloat.greatestFiniteMagnitude),
|
||||||
transition: .immediate
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
let text = text.update(
|
let text = text.update(
|
||||||
component: MultilineTextComponent(
|
component: AnimatedTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
font: Font.regular(14.0),
|
||||||
string: component.strings.HashtagSearch_StoriesFoundInfo(component.query).string,
|
color: component.theme.rootController.navigationBar.secondaryTextColor,
|
||||||
font: Font.regular(14.0),
|
items: textString,
|
||||||
textColor: component.theme.rootController.navigationBar.secondaryTextColor,
|
noDelay: true
|
||||||
paragraphAlignment: .natural
|
|
||||||
)),
|
|
||||||
horizontalAlignment: .natural,
|
|
||||||
maximumNumberOfLines: 1
|
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: context.availableSize.width - textLeftInset, height: context.availableSize.height),
|
availableSize: CGSize(width: context.availableSize.width - textLeftInset, height: context.availableSize.height),
|
||||||
transition: .immediate
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
let arrow = arrow.update(
|
let arrow = arrow.update(
|
||||||
@ -162,7 +208,7 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
let size = CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + spacing + text.size.height + textTopInset + 2.0)
|
let size = CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + spacing + text.size.height + textTopInset + 2.0)
|
||||||
|
|
||||||
let background = background.update(
|
let background = background.update(
|
||||||
component: Rectangle(color: component.theme.rootController.navigationBar.opaqueBackgroundColor),
|
component: BlurredBackgroundComponent(color: component.theme.rootController.navigationBar.blurredBackgroundColor),
|
||||||
availableSize: size,
|
availableSize: size,
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
@ -190,12 +236,37 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
|||||||
.position(CGPoint(x: background.size.width / 2.0, y: background.size.height - separator.size.height / 2.0))
|
.position(CGPoint(x: background.size.width / 2.0, y: background.size.height - separator.size.height / 2.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(avatars
|
if !items.isEmpty {
|
||||||
.position(CGPoint(x: component.sideInset + 10.0 + 30.0, y: background.size.height / 2.0))
|
let avatars = avatars.update(
|
||||||
)
|
component: StorySetIndicatorComponent(
|
||||||
|
context: component.context,
|
||||||
|
strings: component.strings,
|
||||||
|
items: Array(items.prefix(3)),
|
||||||
|
displayAvatars: component.peer == nil,
|
||||||
|
hasUnseen: true,
|
||||||
|
hasUnseenPrivate: false,
|
||||||
|
totalCount: 0,
|
||||||
|
theme: component.theme,
|
||||||
|
action: {}
|
||||||
|
),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(avatars
|
||||||
|
.position(CGPoint(x: component.sideInset + 10.0 + 30.0, y: background.size.height / 2.0))
|
||||||
|
.appear(.default(scale: true, alpha: true))
|
||||||
|
.disappear(.default(scale: true, alpha: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let titlePrefixChild {
|
||||||
|
context.add(titlePrefixChild
|
||||||
|
.position(CGPoint(x: textLeftInset + titlePrefixChild.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
context.add(title
|
context.add(title
|
||||||
.position(CGPoint(x: textLeftInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
.position(CGPoint(x: textLeftInset + titlePrefixOffset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(text
|
context.add(text
|
||||||
|
@ -565,7 +565,7 @@ public func legacyAttachmentMenu(
|
|||||||
|
|
||||||
carouselItemView?.underlyingViews = underlyingViews
|
carouselItemView?.underlyingViews = underlyingViews
|
||||||
|
|
||||||
if editMediaOptions == nil {
|
if editMediaOptions == nil && !addingMedia {
|
||||||
for i in 0 ..< min(20, recentlyUsedInlineBots.count) {
|
for i in 0 ..< min(20, recentlyUsedInlineBots.count) {
|
||||||
let peer = recentlyUsedInlineBots[i]
|
let peer = recentlyUsedInlineBots[i]
|
||||||
let addressName = peer.addressName
|
let addressName = peer.addressName
|
||||||
|
@ -13,6 +13,7 @@ swift_library(
|
|||||||
"//submodules/SSignalKit/SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/Display",
|
"//submodules/Display",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
public final class AnimatedTextComponent: Component {
|
public final class AnimatedTextComponent: Component {
|
||||||
public struct Item: Equatable {
|
public struct Item: Equatable {
|
||||||
@ -24,15 +25,18 @@ public final class AnimatedTextComponent: Component {
|
|||||||
public let font: UIFont
|
public let font: UIFont
|
||||||
public let color: UIColor
|
public let color: UIColor
|
||||||
public let items: [Item]
|
public let items: [Item]
|
||||||
|
public let noDelay: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
font: UIFont,
|
font: UIFont,
|
||||||
color: UIColor,
|
color: UIColor,
|
||||||
items: [Item]
|
items: [Item],
|
||||||
|
noDelay: Bool = false
|
||||||
) {
|
) {
|
||||||
self.font = font
|
self.font = font
|
||||||
self.color = color
|
self.color = color
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.noDelay = noDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: AnimatedTextComponent, rhs: AnimatedTextComponent) -> Bool {
|
public static func ==(lhs: AnimatedTextComponent, rhs: AnimatedTextComponent) -> Bool {
|
||||||
@ -45,6 +49,9 @@ public final class AnimatedTextComponent: Component {
|
|||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.noDelay != rhs.noDelay {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +164,12 @@ public final class AnimatedTextComponent: Component {
|
|||||||
|
|
||||||
if animateIn, !transition.animation.isImmediate {
|
if animateIn, !transition.animation.isImmediate {
|
||||||
var delayWidth: Double = 0.0
|
var delayWidth: Double = 0.0
|
||||||
if let firstDelayWidth {
|
if !component.noDelay {
|
||||||
delayWidth = size.width - firstDelayWidth
|
if let firstDelayWidth {
|
||||||
} else {
|
delayWidth = size.width - firstDelayWidth
|
||||||
firstDelayWidth = size.width
|
} else {
|
||||||
|
firstDelayWidth = size.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring)
|
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
@ -220,3 +229,33 @@ public final class AnimatedTextComponent: Component {
|
|||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension AnimatedTextComponent {
|
||||||
|
static func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] {
|
||||||
|
var textItems: [AnimatedTextComponent.Item] = []
|
||||||
|
|
||||||
|
var previousIndex = 0
|
||||||
|
let nsString = string.string as NSString
|
||||||
|
for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
|
||||||
|
if range.range.lowerBound > previousIndex {
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
|
||||||
|
}
|
||||||
|
if let value = mapping[range.index] {
|
||||||
|
let isUnbreakable: Bool
|
||||||
|
switch value {
|
||||||
|
case .text:
|
||||||
|
isUnbreakable = true
|
||||||
|
case .number:
|
||||||
|
isUnbreakable = false
|
||||||
|
}
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value))
|
||||||
|
}
|
||||||
|
previousIndex = range.range.upperBound
|
||||||
|
}
|
||||||
|
if nsString.length > previousIndex {
|
||||||
|
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
return textItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1025,7 +1025,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
let premiumGiftOptions: Signal<[PremiumGiftCodeOption], NoError>
|
let premiumGiftOptions: Signal<[PremiumGiftCodeOption], NoError>
|
||||||
let profileGiftsContext: ProfileGiftsContext?
|
let profileGiftsContext: ProfileGiftsContext?
|
||||||
if case .user = kind {
|
if case .user = kind {
|
||||||
profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: userPeerId)
|
if isMyProfile || userPeerId != context.account.peerId {
|
||||||
|
profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: userPeerId)
|
||||||
|
} else {
|
||||||
|
profileGiftsContext = nil
|
||||||
|
}
|
||||||
premiumGiftOptions = .single([])
|
premiumGiftOptions = .single([])
|
||||||
|> then(
|
|> then(
|
||||||
context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true)
|
context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true)
|
||||||
@ -1314,7 +1318,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
availablePanes?.insert(.stories, at: 0)
|
availablePanes?.insert(.stories, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData {
|
if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData, peerView.peerId != context.account.peerId {
|
||||||
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
|
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
|
||||||
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
|
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
|
||||||
}
|
}
|
||||||
|
@ -1770,7 +1770,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
|
|
||||||
if case .peer = self.scope {
|
if case .peer = self.scope {
|
||||||
let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager)
|
let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] count in
|
|> deliverOnMainQueue).start(next: { [weak self] count in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2420,7 +2420,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.updateDisposable.dispose()
|
self.updateDisposable.dispose()
|
||||||
self.mapDisposable?.dispose()
|
self.mapDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
||||||
let listSource = self.listSource
|
let listSource = self.listSource
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
@ -2862,6 +2862,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var authorPeer = item.peer
|
||||||
var isReorderable = false
|
var isReorderable = false
|
||||||
switch self.scope {
|
switch self.scope {
|
||||||
case .botPreview:
|
case .botPreview:
|
||||||
@ -2870,16 +2871,20 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
if id == self.context.account.peerId {
|
if id == self.context.account.peerId {
|
||||||
isReorderable = state.pinnedIds.contains(item.storyItem.id)
|
isReorderable = state.pinnedIds.contains(item.storyItem.id)
|
||||||
}
|
}
|
||||||
|
case let .search(peerId, _):
|
||||||
|
if peerId != nil {
|
||||||
|
authorPeer = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedItems.append(VisualMediaItem(
|
mappedItems.append(VisualMediaItem(
|
||||||
index: mappedItems.count,
|
index: mappedItems.count,
|
||||||
peer: peerReference,
|
peer: peerReference,
|
||||||
storyId: item.id,
|
storyId: item.id,
|
||||||
story: item.storyItem,
|
story: item.storyItem,
|
||||||
authorPeer: item.peer,
|
authorPeer: authorPeer,
|
||||||
isPinned: state.pinnedIds.contains(item.storyItem.id),
|
isPinned: state.pinnedIds.contains(item.storyItem.id),
|
||||||
localMonthTimestamp: Month(localTimestamp: item.storyItem.timestamp + timezoneOffset).packedValue,
|
localMonthTimestamp: Month(localTimestamp: item.storyItem.timestamp + timezoneOffset).packedValue,
|
||||||
isReorderable: isReorderable
|
isReorderable: isReorderable
|
||||||
@ -4104,7 +4109,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||||
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||||
} else {
|
} else {
|
||||||
self.view.backgroundColor = .clear
|
if case let .search(peerId, _) = self.scope, peerId != nil {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.view.backgroundColor = .clear
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2922,7 +2922,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(nil, hashtag), listContext: nil)
|
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(nil, hashtag), listContext: nil)
|
||||||
navigationController.pushViewController(searchController)
|
navigationController.pushViewController(searchController)
|
||||||
} else {
|
} else {
|
||||||
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true)
|
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, stories: true, forceDark: true)
|
||||||
navigationController.pushViewController(searchController)
|
navigationController.pushViewController(searchController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,6 +330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
public let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
public let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
public let searchResultsCount = ValuePromise<Int32>(0, ignoreRepeated: true)
|
||||||
let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
|
let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
|
||||||
let loadingMessage = Promise<ChatLoadingMessageSubject?>(nil)
|
let loadingMessage = Promise<ChatLoadingMessageSubject?>(nil)
|
||||||
let performingInlineSearch = ValuePromise<Bool>(false, ignoreRepeated: true)
|
let performingInlineSearch = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
@ -629,6 +630,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var externalSearchResultsCount: Int32? {
|
||||||
|
didSet {
|
||||||
|
if let panelNode = self.chatDisplayNode.inputPanelNode as? ChatTagSearchInputPanelNode {
|
||||||
|
panelNode.externalSearchResultsCount = self.externalSearchResultsCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var includeSavedPeersInSearchResults: Bool = false {
|
public var includeSavedPeersInSearchResults: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
self.chatDisplayNode.includeSavedPeersInSearchResults = self.includeSavedPeersInSearchResults
|
self.chatDisplayNode.includeSavedPeersInSearchResults = self.includeSavedPeersInSearchResults
|
||||||
@ -9697,7 +9706,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else if case let .customChatContents(contents) = self.subject, case let .hashTagSearch(publicPostsValue) = contents.kind {
|
} else if case let .customChatContents(contents) = self.subject, case let .hashTagSearch(publicPostsValue) = contents.kind {
|
||||||
publicPosts = publicPostsValue
|
publicPosts = publicPostsValue
|
||||||
}
|
}
|
||||||
let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, mode: peerName != nil ? .chatOnly : .generic, publicPosts: publicPosts)
|
let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, mode: peerName != nil ? .chatOnly : .generic, publicPosts: peerName == nil && publicPosts)
|
||||||
self.effectiveNavigationController?.pushViewController(searchController)
|
self.effectiveNavigationController?.pushViewController(searchController)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -10972,4 +10981,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var contentContainerNode: ASDisplayNode {
|
||||||
|
return self.chatDisplayNode.contentContainerNode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -513,7 +513,7 @@ extension ChatControllerImpl {
|
|||||||
title = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount))
|
title = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
let textItems = extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [
|
let textItems = AnimatedTextComponent.extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [
|
||||||
0: .number(self.currentSendStarsUndoCount, minDigits: 1),
|
0: .number(self.currentSendStarsUndoCount, minDigits: 1),
|
||||||
1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount)))
|
1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount)))
|
||||||
])
|
])
|
||||||
@ -536,31 +536,3 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] {
|
|
||||||
var textItems: [AnimatedTextComponent.Item] = []
|
|
||||||
|
|
||||||
var previousIndex = 0
|
|
||||||
let nsString = string.string as NSString
|
|
||||||
for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
|
|
||||||
if range.range.lowerBound > previousIndex {
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
|
|
||||||
}
|
|
||||||
if let value = mapping[range.index] {
|
|
||||||
let isUnbreakable: Bool
|
|
||||||
switch value {
|
|
||||||
case .text:
|
|
||||||
isUnbreakable = true
|
|
||||||
case .number:
|
|
||||||
isUnbreakable = false
|
|
||||||
}
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value))
|
|
||||||
}
|
|
||||||
previousIndex = range.range.upperBound
|
|
||||||
}
|
|
||||||
if nsString.length > previousIndex {
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
return textItems
|
|
||||||
}
|
|
||||||
|
@ -77,6 +77,7 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
if queryIsEmpty {
|
if queryIsEmpty {
|
||||||
self.searching.set(false)
|
self.searching.set(false)
|
||||||
|
self.searchResultsCount.set(0)
|
||||||
self.searchDisposable?.set(nil)
|
self.searchDisposable?.set(nil)
|
||||||
self.searchResult.set(.single(nil))
|
self.searchResult.set(.single(nil))
|
||||||
if let data = interfaceState.search {
|
if let data = interfaceState.search {
|
||||||
@ -104,6 +105,7 @@ extension ChatControllerImpl {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.searchResultsCount.set(results.totalCount)
|
||||||
var navigateIndex: MessageIndex?
|
var navigateIndex: MessageIndex?
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||||
if let data = current.search {
|
if let data = current.search {
|
||||||
@ -152,6 +154,7 @@ extension ChatControllerImpl {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
strongSelf.searchResultsCount.set(results.totalCount)
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||||
if let data = current.search, let previousResultsState = data.resultsState {
|
if let data = current.search, let previousResultsState = data.resultsState {
|
||||||
let messageIndices = results.messages.map({ $0.index }).sorted()
|
let messageIndices = results.messages.map({ $0.index }).sorted()
|
||||||
@ -167,11 +170,13 @@ extension ChatControllerImpl {
|
|||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
self.searching.set(false)
|
self.searching.set(false)
|
||||||
|
self.searchResultsCount.set(0)
|
||||||
self.searchDisposable?.set(nil)
|
self.searchDisposable?.set(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.searching.set(false)
|
self.searching.set(false)
|
||||||
|
self.searchResultsCount.set(0)
|
||||||
self.searchDisposable?.set(nil)
|
self.searchDisposable?.set(nil)
|
||||||
|
|
||||||
if let data = interfaceState.search {
|
if let data = interfaceState.search {
|
||||||
|
@ -19,34 +19,6 @@ import AnimatedTextComponent
|
|||||||
|
|
||||||
private let labelFont = Font.regular(15.0)
|
private let labelFont = Font.regular(15.0)
|
||||||
|
|
||||||
private func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] {
|
|
||||||
var textItems: [AnimatedTextComponent.Item] = []
|
|
||||||
|
|
||||||
var previousIndex = 0
|
|
||||||
let nsString = string.string as NSString
|
|
||||||
for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) {
|
|
||||||
if range.range.lowerBound > previousIndex {
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex)))))
|
|
||||||
}
|
|
||||||
if let value = mapping[range.index] {
|
|
||||||
let isUnbreakable: Bool
|
|
||||||
switch value {
|
|
||||||
case .text:
|
|
||||||
isUnbreakable = true
|
|
||||||
case .number:
|
|
||||||
isUnbreakable = false
|
|
||||||
}
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value))
|
|
||||||
}
|
|
||||||
previousIndex = range.range.upperBound
|
|
||||||
}
|
|
||||||
if nsString.length > previousIndex {
|
|
||||||
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
return textItems
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
var width: CGFloat
|
var width: CGFloat
|
||||||
@ -100,6 +72,14 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
private var totalMessageCount: Int?
|
private var totalMessageCount: Int?
|
||||||
private var totalMessageCountDisposable: Disposable?
|
private var totalMessageCountDisposable: Disposable?
|
||||||
|
|
||||||
|
public var externalSearchResultsCount: Int32? {
|
||||||
|
didSet {
|
||||||
|
if let params = self.currentLayout?.params {
|
||||||
|
let _ = self.update(params: params, transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
||||||
didSet {
|
didSet {
|
||||||
}
|
}
|
||||||
@ -223,7 +203,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
var canChangeListMode = false
|
var canChangeListMode = false
|
||||||
|
|
||||||
var resultsTextString: [AnimatedTextComponent.Item] = []
|
var resultsTextString: [AnimatedTextComponent.Item] = []
|
||||||
if let results = params.interfaceState.search?.resultsState {
|
if let externalSearchResultsCount = self.externalSearchResultsCount {
|
||||||
|
let value = presentationStringsFormattedNumber(externalSearchResultsCount, params.interfaceState.dateTimeFormat.groupingSeparator)
|
||||||
|
let suffix = params.interfaceState.strings.Chat_BottomSearchPanel_StoryCount(externalSearchResultsCount)
|
||||||
|
resultsTextString = [AnimatedTextComponent.Item(
|
||||||
|
id: "stories",
|
||||||
|
isUnbreakable: true,
|
||||||
|
content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string)
|
||||||
|
)]
|
||||||
|
} else if let results = params.interfaceState.search?.resultsState {
|
||||||
let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount)
|
let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount)
|
||||||
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
||||||
canChangeListMode = true
|
canChangeListMode = true
|
||||||
@ -237,7 +225,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string)
|
content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string)
|
||||||
)]
|
)]
|
||||||
} else if params.interfaceState.displayHistoryFilterAsList {
|
} else if params.interfaceState.displayHistoryFilterAsList {
|
||||||
resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(
|
resultsTextString = AnimatedTextComponent.extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(
|
||||||
".",
|
".",
|
||||||
"."
|
"."
|
||||||
), id: "total_count", mapping: [
|
), id: "total_count", mapping: [
|
||||||
@ -247,7 +235,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
} else {
|
} else {
|
||||||
let adjustedIndex = results.messageIndices.count - 1 - index
|
let adjustedIndex = results.messageIndices.count - 1 - index
|
||||||
|
|
||||||
resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Items_NOfM(
|
resultsTextString = AnimatedTextComponent.extractAnimatedTextString(string: params.interfaceState.strings.Items_NOfM(
|
||||||
".",
|
".",
|
||||||
"."
|
"."
|
||||||
), id: "position", mapping: [
|
), id: "position", mapping: [
|
||||||
@ -263,7 +251,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
} else if let count = self.tagMessageCount?.count ?? self.totalMessageCount {
|
} else if let count = self.tagMessageCount?.count ?? self.totalMessageCount {
|
||||||
canChangeListMode = count != 0
|
canChangeListMode = count != 0
|
||||||
|
|
||||||
resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(
|
resultsTextString = AnimatedTextComponent.extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(
|
||||||
".",
|
".",
|
||||||
"."
|
"."
|
||||||
), id: "total_count", mapping: [
|
), id: "total_count", mapping: [
|
||||||
@ -282,7 +270,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var modeButtonTitle: [AnimatedTextComponent.Item] = []
|
var modeButtonTitle: [AnimatedTextComponent.Item] = []
|
||||||
modeButtonTitle = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeFormat("."), id: "mode", mapping: [
|
modeButtonTitle = AnimatedTextComponent.extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeFormat("."), id: "mode", mapping: [
|
||||||
0: params.interfaceState.displayHistoryFilterAsList ? .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeChat) : .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeList)
|
0: params.interfaceState.displayHistoryFilterAsList ? .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeChat) : .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeList)
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -346,7 +334,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
|
|
||||||
var nextLeftX: CGFloat = 16.0
|
var nextLeftX: CGFloat = 16.0
|
||||||
|
|
||||||
if !self.alwaysShowTotalMessagesCount {
|
if !self.alwaysShowTotalMessagesCount && self.externalSearchResultsCount == nil {
|
||||||
nextLeftX = 12.0
|
nextLeftX = 12.0
|
||||||
let calendarButtonSize = self.calendarButton.update(
|
let calendarButtonSize = self.calendarButton.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
@ -372,12 +360,28 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
if let calendarButtonView = self.calendarButton.view {
|
if let calendarButtonView = self.calendarButton.view {
|
||||||
if calendarButtonView.superview == nil {
|
if calendarButtonView.superview == nil {
|
||||||
self.view.addSubview(calendarButtonView)
|
self.view.addSubview(calendarButtonView)
|
||||||
|
|
||||||
|
if !transition.animation.isImmediate {
|
||||||
|
calendarButtonView.alpha = 1.0
|
||||||
|
transition.animateAlpha(view: calendarButtonView, from: 0.0, to: 1.0)
|
||||||
|
transition.animateScale(view: calendarButtonView, from: 0.01, to: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame)
|
transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame)
|
||||||
}
|
}
|
||||||
nextLeftX += calendarButtonSize.width + 8.0
|
nextLeftX += calendarButtonSize.width + 8.0
|
||||||
} else if let calendarButtonView = self.calendarButton.view {
|
} else if let calendarButtonView = self.calendarButton.view {
|
||||||
calendarButtonView.removeFromSuperview()
|
if transition.animation.isImmediate {
|
||||||
|
calendarButtonView.removeFromSuperview()
|
||||||
|
} else {
|
||||||
|
transition.setAlpha(view: calendarButtonView, alpha: 0.0, completion: { finished in
|
||||||
|
if finished {
|
||||||
|
calendarButtonView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
calendarButtonView.alpha = 1.0
|
||||||
|
})
|
||||||
|
transition.animateScale(view: calendarButtonView, from: 1.0, to: 0.01)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if displaySearchMembers {
|
if displaySearchMembers {
|
||||||
|
@ -1924,8 +1924,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return inputPanelNode
|
return inputPanelNode
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController {
|
public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController {
|
||||||
return HashtagSearchController(context: context, peer: peer, query: query, mode: all ? .noChat : .generic)
|
return HashtagSearchController(context: context, peer: peer, query: query, mode: stories ? .chatOnly : .generic, stories: stories, forceDark: forceDark)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController {
|
public func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController {
|
||||||
|
@ -9,23 +9,27 @@ public struct ChatTranslationState: Codable {
|
|||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case baseLang
|
case baseLang
|
||||||
case fromLang
|
case fromLang
|
||||||
|
case timestamp
|
||||||
case toLang
|
case toLang
|
||||||
case isEnabled
|
case isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
public let baseLang: String
|
public let baseLang: String
|
||||||
public let fromLang: String
|
public let fromLang: String
|
||||||
|
public let timestamp: Int32?
|
||||||
public let toLang: String?
|
public let toLang: String?
|
||||||
public let isEnabled: Bool
|
public let isEnabled: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
baseLang: String,
|
baseLang: String,
|
||||||
fromLang: String,
|
fromLang: String,
|
||||||
|
timestamp: Int32?,
|
||||||
toLang: String?,
|
toLang: String?,
|
||||||
isEnabled: Bool
|
isEnabled: Bool
|
||||||
) {
|
) {
|
||||||
self.baseLang = baseLang
|
self.baseLang = baseLang
|
||||||
self.fromLang = fromLang
|
self.fromLang = fromLang
|
||||||
|
self.timestamp = timestamp
|
||||||
self.toLang = toLang
|
self.toLang = toLang
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
}
|
}
|
||||||
@ -35,6 +39,7 @@ public struct ChatTranslationState: Codable {
|
|||||||
|
|
||||||
self.baseLang = try container.decode(String.self, forKey: .baseLang)
|
self.baseLang = try container.decode(String.self, forKey: .baseLang)
|
||||||
self.fromLang = try container.decode(String.self, forKey: .fromLang)
|
self.fromLang = try container.decode(String.self, forKey: .fromLang)
|
||||||
|
self.timestamp = try container.decodeIfPresent(Int32.self, forKey: .timestamp)
|
||||||
self.toLang = try container.decodeIfPresent(String.self, forKey: .toLang)
|
self.toLang = try container.decodeIfPresent(String.self, forKey: .toLang)
|
||||||
self.isEnabled = try container.decode(Bool.self, forKey: .isEnabled)
|
self.isEnabled = try container.decode(Bool.self, forKey: .isEnabled)
|
||||||
}
|
}
|
||||||
@ -44,6 +49,7 @@ public struct ChatTranslationState: Codable {
|
|||||||
|
|
||||||
try container.encode(self.baseLang, forKey: .baseLang)
|
try container.encode(self.baseLang, forKey: .baseLang)
|
||||||
try container.encode(self.fromLang, forKey: .fromLang)
|
try container.encode(self.fromLang, forKey: .fromLang)
|
||||||
|
try container.encodeIfPresent(self.timestamp, forKey: .timestamp)
|
||||||
try container.encodeIfPresent(self.toLang, forKey: .toLang)
|
try container.encodeIfPresent(self.toLang, forKey: .toLang)
|
||||||
try container.encode(self.isEnabled, forKey: .isEnabled)
|
try container.encode(self.isEnabled, forKey: .isEnabled)
|
||||||
}
|
}
|
||||||
@ -52,6 +58,7 @@ public struct ChatTranslationState: Codable {
|
|||||||
return ChatTranslationState(
|
return ChatTranslationState(
|
||||||
baseLang: self.baseLang,
|
baseLang: self.baseLang,
|
||||||
fromLang: self.fromLang,
|
fromLang: self.fromLang,
|
||||||
|
timestamp: self.timestamp,
|
||||||
toLang: toLang,
|
toLang: toLang,
|
||||||
isEnabled: self.isEnabled
|
isEnabled: self.isEnabled
|
||||||
)
|
)
|
||||||
@ -61,6 +68,7 @@ public struct ChatTranslationState: Codable {
|
|||||||
return ChatTranslationState(
|
return ChatTranslationState(
|
||||||
baseLang: self.baseLang,
|
baseLang: self.baseLang,
|
||||||
fromLang: self.fromLang,
|
fromLang: self.fromLang,
|
||||||
|
timestamp: self.timestamp,
|
||||||
toLang: self.toLang,
|
toLang: self.toLang,
|
||||||
isEnabled: isEnabled
|
isEnabled: isEnabled
|
||||||
)
|
)
|
||||||
@ -191,7 +199,8 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
|||||||
|
|
||||||
return cachedChatTranslationState(engine: context.engine, peerId: peerId)
|
return cachedChatTranslationState(engine: context.engine, peerId: peerId)
|
||||||
|> mapToSignal { cached in
|
|> mapToSignal { cached in
|
||||||
if let cached, cached.baseLang == baseLang {
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
if let cached, let timestamp = cached.timestamp, cached.baseLang == baseLang && currentTime - timestamp < 60 * 60 {
|
||||||
if !dontTranslateLanguages.contains(cached.fromLang) {
|
if !dontTranslateLanguages.contains(cached.fromLang) {
|
||||||
return .single(cached)
|
return .single(cached)
|
||||||
} else {
|
} else {
|
||||||
@ -277,7 +286,13 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
|||||||
if loggingEnabled {
|
if loggingEnabled {
|
||||||
Logger.shared.log("ChatTranslation", "Ended with: \(fromLang)")
|
Logger.shared.log("ChatTranslation", "Ended with: \(fromLang)")
|
||||||
}
|
}
|
||||||
let state = ChatTranslationState(baseLang: baseLang, fromLang: fromLang, toLang: nil, isEnabled: false)
|
let state = ChatTranslationState(
|
||||||
|
baseLang: baseLang,
|
||||||
|
fromLang: fromLang,
|
||||||
|
timestamp: currentTime,
|
||||||
|
toLang: cached?.toLang,
|
||||||
|
isEnabled: cached?.isEnabled ?? false
|
||||||
|
)
|
||||||
let _ = updateChatTranslationState(engine: context.engine, peerId: peerId, state: state).start()
|
let _ = updateChatTranslationState(engine: context.engine, peerId: peerId, state: state).start()
|
||||||
if !dontTranslateLanguages.contains(fromLang) {
|
if !dontTranslateLanguages.contains(fromLang) {
|
||||||
return state
|
return state
|
||||||
|
@ -587,6 +587,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var updateWebViewWhenStable = false
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
let previousLayout = self.validLayout?.0
|
let previousLayout = self.validLayout?.0
|
||||||
@ -605,6 +607,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight - frameBottomInset)))
|
let frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight - frameBottomInset)))
|
||||||
|
if !webView.frame.width.isZero && webView.frame != frame {
|
||||||
|
self.updateWebViewWhenStable = true
|
||||||
|
}
|
||||||
|
|
||||||
var bottomInset = layout.intrinsicInsets.bottom + layout.additionalInsets.bottom
|
var bottomInset = layout.intrinsicInsets.bottom + layout.additionalInsets.bottom
|
||||||
if let inputHeight = self.validLayout?.0.inputHeight, inputHeight > 44.0 {
|
if let inputHeight = self.validLayout?.0.inputHeight, inputHeight > 44.0 {
|
||||||
@ -635,6 +640,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
if let controller = self.controller {
|
if let controller = self.controller {
|
||||||
webView.updateMetrics(height: viewportFrame.height, isExpanded: controller.isContainerExpanded(), isStable: !controller.isContainerPanning(), transition: transition)
|
webView.updateMetrics(height: viewportFrame.height, isExpanded: controller.isContainerExpanded(), isStable: !controller.isContainerPanning(), transition: transition)
|
||||||
|
if self.updateWebViewWhenStable && !controller.isContainerPanning() {
|
||||||
|
self.updateWebViewWhenStable = false
|
||||||
|
webView.setNeedsLayout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.intrinsicInsets.bottom > 44.0 {
|
if layout.intrinsicInsets.bottom > 44.0 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user