mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35: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_any" = "messages";
|
||||
|
||||
"Chat.BottomSearchPanel.StoryCount_1" = "story";
|
||||
"Chat.BottomSearchPanel.StoryCount_any" = "stories";
|
||||
|
||||
"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@";
|
||||
"Chat.BottomSearchPanel.DisplayModeChat" = "Chat";
|
||||
"Chat.BottomSearchPanel.DisplayModeList" = "List";
|
||||
@ -12397,6 +12400,12 @@ Sorry for the inconvenience.";
|
||||
"HashtagSearch.Stories_any" = "%@ Stories";
|
||||
"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.Revenue.Title" = "Stars Received";
|
||||
"Stars.BotRevenue.Proceeds.Title" = "Rewards Overview";
|
||||
|
@ -941,7 +941,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
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 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 makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
||||
|
@ -1031,14 +1031,18 @@ public protocol ChatController: ViewController {
|
||||
|
||||
var visibleContextController: ViewController? { get }
|
||||
|
||||
var contentContainerNode: ASDisplayNode { get }
|
||||
|
||||
var searching: ValuePromise<Bool> { get }
|
||||
var searchResultsCount: ValuePromise<Int32> { get }
|
||||
var externalSearchResultsCount: Int32? { get set }
|
||||
|
||||
var alwaysShowSearchResultsAsList: Bool { get set }
|
||||
var includeSavedPeersInSearchResults: Bool { get set }
|
||||
var showListEmptyResults: Bool { get set }
|
||||
func beginMessageSearch(_ query: String)
|
||||
|
||||
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
||||
func beginMessageSearch(_ query: String)
|
||||
func displayPromoAnnouncement(text: String)
|
||||
|
||||
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
|
||||
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 {
|
||||
if case let .filter(_, title, _, data) = filter {
|
||||
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)
|
||||
})))
|
||||
items.append(.separator)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,10 @@ swift_library(
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -25,6 +25,8 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
private let query: String
|
||||
let mode: Mode
|
||||
let publicPosts: Bool
|
||||
let stories: Bool
|
||||
let forceDark: Bool
|
||||
|
||||
private var transitionDisposable: Disposable?
|
||||
private let openMessageFromSearchDisposable = MetaDisposable()
|
||||
@ -39,17 +41,23 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
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.peer = peer
|
||||
self.query = query
|
||||
self.mode = mode
|
||||
self.publicPosts = publicPosts
|
||||
self.stories = stories
|
||||
self.forceDark = forceDark
|
||||
|
||||
self.animationCache = context.animationCache
|
||||
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)
|
||||
|
||||
@ -69,6 +77,11 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
let previousTheme = self.presentationData.theme
|
||||
let previousStrings = self.presentationData.strings
|
||||
|
||||
var presentationData = presentationData
|
||||
if forceDark {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
|
@ -9,6 +9,8 @@ import AccountContext
|
||||
import ChatListUI
|
||||
import SegmentedControlNode
|
||||
import ChatListSearchItemHeader
|
||||
import PeerInfoVisualMediaPaneNode
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
@ -30,6 +32,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
private let isSearching = Promise<Bool>()
|
||||
private var isSearchingDisposable: Disposable?
|
||||
|
||||
private var searchResultsCount: Int32 = 0
|
||||
private var searchResultsCountDisposable: Disposable?
|
||||
|
||||
private let clippingNode: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
let currentController: ChatController?
|
||||
@ -39,10 +44,13 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
let globalController: ChatController?
|
||||
let globalChatContents: HashtagSearchGlobalChatContents?
|
||||
|
||||
private var globalStorySearchContext: SearchStoryListContext?
|
||||
private var globalStorySearchDisposable = MetaDisposable()
|
||||
private var globalStorySearchState: StoryListContext.State?
|
||||
private var globalStorySearchComponentView: ComponentView<Empty>?
|
||||
private var storySearchContext: SearchStoryListContext?
|
||||
private var storySearchDisposable = MetaDisposable()
|
||||
private var storySearchState: StoryListContext.State?
|
||||
private var storySearchComponentView: ComponentView<Empty>?
|
||||
|
||||
private var storySearchPaneNode: PeerInfoStoryPaneNode?
|
||||
private var isDisplayingStories = false
|
||||
|
||||
private var panRecognizer: InteractiveTransitionGestureRecognizer?
|
||||
|
||||
@ -57,8 +65,14 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
self.navigationBar = navigationBar
|
||||
self.isCashtag = query.hasPrefix("$")
|
||||
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.clipsToBounds = true
|
||||
@ -78,7 +92,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
|
||||
let navigationController = controller.navigationController as? NavigationController
|
||||
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?.showListEmptyResults = true
|
||||
self.currentController?.customNavigationController = navigationController
|
||||
@ -89,7 +103,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
if let _ = peer, controller.mode != .chatOnly {
|
||||
let myChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: false)
|
||||
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?.showListEmptyResults = true
|
||||
self.myController?.customNavigationController = navigationController
|
||||
@ -100,7 +114,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
|
||||
let globalChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: true)
|
||||
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?.showListEmptyResults = true
|
||||
self.globalController?.customNavigationController = navigationController
|
||||
@ -182,7 +196,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
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?.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()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.searchQueryDisposable?.dispose()
|
||||
self.isSearchingDisposable?.dispose()
|
||||
self.globalStorySearchDisposable.dispose()
|
||||
self.searchResultsCountDisposable?.dispose()
|
||||
self.storySearchDisposable.dispose()
|
||||
}
|
||||
|
||||
private var panAllowedDirections: InteractiveTransitionGestureRecognizerDirections {
|
||||
@ -373,29 +401,30 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
}
|
||||
|
||||
private func updateStorySearch() {
|
||||
self.globalStorySearchState = nil
|
||||
self.globalStorySearchDisposable.set(nil)
|
||||
self.globalStorySearchContext = nil
|
||||
self.storySearchState = nil
|
||||
self.storySearchDisposable.set(nil)
|
||||
self.storySearchContext = nil
|
||||
|
||||
if !self.query.isEmpty {
|
||||
var peerId: EnginePeer.Id?
|
||||
if self.controller?.mode == .chatOnly {
|
||||
peerId = self.peer?.id
|
||||
}
|
||||
let globalStorySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(peerId, self.query))
|
||||
self.globalStorySearchDisposable.set((globalStorySearchContext.state
|
||||
let storySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(peerId, self.query))
|
||||
self.storySearchDisposable.set((storySearchContext.state
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if state.totalCount > 0 {
|
||||
self.globalStorySearchState = state
|
||||
self.storySearchState = state
|
||||
} 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) {
|
||||
if let (layout, navigationHeight) = self.containerLayout {
|
||||
let _ = self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
|
||||
@ -440,45 +500,69 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
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 globalTopInset: CGFloat = 0.0
|
||||
if let state = self.globalStorySearchState {
|
||||
var parentController: ViewController?
|
||||
if self.controller?.mode == .chatOnly {
|
||||
parentController = self.currentController
|
||||
|
||||
var panelSearchState: StoryResultsPanelComponent.SearchState?
|
||||
if let storySearchState = self.storySearchState {
|
||||
if self.isDisplayingStories {
|
||||
if self.searchResultsCount > 0 {
|
||||
panelSearchState = .messages(self.searchResultsCount)
|
||||
}
|
||||
} 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>
|
||||
var panelTransition = ComponentTransition(transition)
|
||||
if let current = self.globalStorySearchComponentView {
|
||||
if let current = self.storySearchComponentView {
|
||||
componentView = current
|
||||
} else {
|
||||
panelTransition = .immediate
|
||||
componentView = ComponentView()
|
||||
self.globalStorySearchComponentView = componentView
|
||||
self.storySearchComponentView = componentView
|
||||
}
|
||||
let panelSize = componentView.update(
|
||||
transition: .immediate,
|
||||
transition: panelTransition,
|
||||
component: AnyComponent(StoryResultsPanelComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
query: self.query,
|
||||
peer: self.controller?.mode == .chatOnly ? self.peer : nil,
|
||||
state: state,
|
||||
state: panelSearchState,
|
||||
sideInset: layout.safeInsets.left,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var peer: EnginePeer?
|
||||
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: {},
|
||||
@ -487,8 +571,8 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top - 36.0), size: panelSize)
|
||||
if let view = componentView.view {
|
||||
if view.superview == nil {
|
||||
parentController.view.addSubview(view)
|
||||
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -panelSize.height), to: .zero, duration: 0.25, additive: true)
|
||||
storyParentController.view.addSubview(view)
|
||||
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)
|
||||
}
|
||||
@ -498,9 +582,9 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg
|
||||
globalTopInset += panelSize.height
|
||||
}
|
||||
}
|
||||
} else if let globalStorySearchComponentView = self.globalStorySearchComponentView {
|
||||
globalStorySearchComponentView.view?.removeFromSuperview()
|
||||
self.globalStorySearchComponentView = nil
|
||||
} else if let storySearchComponentView = self.storySearchComponentView {
|
||||
storySearchComponentView.view?.removeFromSuperview()
|
||||
self.storySearchComponentView = nil
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
|
@ -7,14 +7,20 @@ import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import StorySetIndicatorComponent
|
||||
import AccountContext
|
||||
import AnimatedTextComponent
|
||||
import BlurredBackgroundComponent
|
||||
|
||||
final class StoryResultsPanelComponent: CombinedComponent {
|
||||
enum SearchState: Equatable {
|
||||
case stories(StoryListContext.State)
|
||||
case messages(Int32)
|
||||
}
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let query: String
|
||||
let peer: EnginePeer?
|
||||
let state: StoryListContext.State
|
||||
let state: SearchState
|
||||
let sideInset: CGFloat
|
||||
let action: () -> Void
|
||||
|
||||
@ -24,7 +30,7 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
||||
strings: PresentationStrings,
|
||||
query: String,
|
||||
peer: EnginePeer?,
|
||||
state: StoryListContext.State,
|
||||
state: SearchState,
|
||||
sideInset: CGFloat,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
@ -61,10 +67,11 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(Rectangle.self)
|
||||
let background = Child(BlurredBackgroundComponent.self)
|
||||
let avatars = Child(StorySetIndicatorComponent.self)
|
||||
let titlePrefix = Child(AnimatedTextComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let text = Child(AnimatedTextComponent.self)
|
||||
let arrow = Child(BundleIconComponent.self)
|
||||
let separator = Child(Rectangle.self)
|
||||
let button = Child(Button.self)
|
||||
@ -74,39 +81,47 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
||||
|
||||
let spacing: CGFloat = 3.0
|
||||
|
||||
let textLeftInset: CGFloat = 81.0 + component.sideInset
|
||||
var textLeftInset: CGFloat = 16.0 + component.sideInset
|
||||
let textTopInset: CGFloat = 9.0
|
||||
|
||||
var existingPeerIds = Set<EnginePeer.Id>()
|
||||
var items: [StorySetIndicatorComponent.Item] = []
|
||||
for item in component.state.items {
|
||||
guard let peer = item.peer, !existingPeerIds.contains(peer.id) else {
|
||||
continue
|
||||
switch component.state {
|
||||
case let .stories(state):
|
||||
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)
|
||||
items.append(StorySetIndicatorComponent.Item(storyItem: item.storyItem, peer: peer))
|
||||
textLeftInset += 65.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let avatars = avatars.update(
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
var titlePrefixString: [AnimatedTextComponent.Item] = []
|
||||
let titleString: NSAttributedString
|
||||
var textString: [AnimatedTextComponent.Item] = []
|
||||
if let peer = component.peer, let username = peer.addressName {
|
||||
let storiesString = component.strings.HashtagSearch_Stories(Int32(component.state.totalCount))
|
||||
let fullString = component.strings.HashtagSearch_LocalStoriesFound(storiesString, "@\(username)")
|
||||
let entityType: String
|
||||
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(
|
||||
string: fullString.string,
|
||||
font: Font.semibold(15.0),
|
||||
@ -116,13 +131,48 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
||||
if let lastRange = fullString.ranges.last?.range {
|
||||
(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 {
|
||||
titleString = NSAttributedString(
|
||||
string: component.strings.HashtagSearch_StoriesFound(Int32(component.state.totalCount)),
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.theme.rootController.navigationBar.primaryTextColor,
|
||||
paragraphAlignment: .natural
|
||||
if case let .stories(state) = component.state {
|
||||
titleString = NSAttributedString(
|
||||
string: component.strings.HashtagSearch_StoriesFound(Int32(state.totalCount)),
|
||||
font: Font.semibold(15.0),
|
||||
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(
|
||||
@ -131,23 +181,19 @@ final class StoryResultsPanelComponent: CombinedComponent {
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textLeftInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
availableSize: CGSize(width: context.availableSize.width - textLeftInset - 64.0 - titlePrefixOffset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.strings.HashtagSearch_StoriesFoundInfo(component.query).string,
|
||||
font: Font.regular(14.0),
|
||||
textColor: component.theme.rootController.navigationBar.secondaryTextColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 1
|
||||
component: AnimatedTextComponent(
|
||||
font: Font.regular(14.0),
|
||||
color: component.theme.rootController.navigationBar.secondaryTextColor,
|
||||
items: textString,
|
||||
noDelay: true
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textLeftInset, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
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 background = background.update(
|
||||
component: Rectangle(color: component.theme.rootController.navigationBar.opaqueBackgroundColor),
|
||||
component: BlurredBackgroundComponent(color: component.theme.rootController.navigationBar.blurredBackgroundColor),
|
||||
availableSize: size,
|
||||
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))
|
||||
)
|
||||
|
||||
context.add(avatars
|
||||
.position(CGPoint(x: component.sideInset + 10.0 + 30.0, y: background.size.height / 2.0))
|
||||
)
|
||||
if !items.isEmpty {
|
||||
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
|
||||
.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
|
||||
|
@ -565,7 +565,7 @@ public func legacyAttachmentMenu(
|
||||
|
||||
carouselItemView?.underlyingViews = underlyingViews
|
||||
|
||||
if editMediaOptions == nil {
|
||||
if editMediaOptions == nil && !addingMedia {
|
||||
for i in 0 ..< min(20, recentlyUsedInlineBots.count) {
|
||||
let peer = recentlyUsedInlineBots[i]
|
||||
let addressName = peer.addressName
|
||||
|
@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramPresentationData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class AnimatedTextComponent: Component {
|
||||
public struct Item: Equatable {
|
||||
@ -24,15 +25,18 @@ public final class AnimatedTextComponent: Component {
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let items: [Item]
|
||||
public let noDelay: Bool
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
items: [Item]
|
||||
items: [Item],
|
||||
noDelay: Bool = false
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.items = items
|
||||
self.noDelay = noDelay
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedTextComponent, rhs: AnimatedTextComponent) -> Bool {
|
||||
@ -45,6 +49,9 @@ public final class AnimatedTextComponent: Component {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.noDelay != rhs.noDelay {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -157,10 +164,12 @@ public final class AnimatedTextComponent: Component {
|
||||
|
||||
if animateIn, !transition.animation.isImmediate {
|
||||
var delayWidth: Double = 0.0
|
||||
if let firstDelayWidth {
|
||||
delayWidth = size.width - firstDelayWidth
|
||||
} else {
|
||||
firstDelayWidth = size.width
|
||||
if !component.noDelay {
|
||||
if let firstDelayWidth {
|
||||
delayWidth = size.width - firstDelayWidth
|
||||
} else {
|
||||
firstDelayWidth = size.width
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 profileGiftsContext: ProfileGiftsContext?
|
||||
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([])
|
||||
|> then(
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
|
||||
}
|
||||
|
@ -1770,7 +1770,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||
|
||||
if case .peer = self.scope {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -2420,7 +2420,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||
self.updateDisposable.dispose()
|
||||
self.mapDisposable?.dispose()
|
||||
}
|
||||
|
||||
|
||||
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
||||
let listSource = self.listSource
|
||||
return Signal { subscriber in
|
||||
@ -2862,6 +2862,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||
continue
|
||||
}
|
||||
|
||||
var authorPeer = item.peer
|
||||
var isReorderable = false
|
||||
switch self.scope {
|
||||
case .botPreview:
|
||||
@ -2870,16 +2871,20 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||
if id == self.context.account.peerId {
|
||||
isReorderable = state.pinnedIds.contains(item.storyItem.id)
|
||||
}
|
||||
case let .search(peerId, _):
|
||||
if peerId != nil {
|
||||
authorPeer = nil
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
mappedItems.append(VisualMediaItem(
|
||||
index: mappedItems.count,
|
||||
peer: peerReference,
|
||||
storyId: item.id,
|
||||
story: item.storyItem,
|
||||
authorPeer: item.peer,
|
||||
authorPeer: authorPeer,
|
||||
isPinned: state.pinnedIds.contains(item.storyItem.id),
|
||||
localMonthTimestamp: Month(localTimestamp: item.storyItem.timestamp + timezoneOffset).packedValue,
|
||||
isReorderable: isReorderable
|
||||
@ -4104,7 +4109,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
||||
if self.isProfileEmbedded, case .botPreview = self.scope {
|
||||
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
} 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)
|
||||
navigationController.pushViewController(searchController)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -330,6 +330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
let unblockingPeer = 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 loadingMessage = Promise<ChatLoadingMessageSubject?>(nil)
|
||||
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 {
|
||||
didSet {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}))
|
||||
@ -10972,4 +10981,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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))
|
||||
}
|
||||
|
||||
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),
|
||||
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 {
|
||||
self.searching.set(false)
|
||||
self.searchResultsCount.set(0)
|
||||
self.searchDisposable?.set(nil)
|
||||
self.searchResult.set(.single(nil))
|
||||
if let data = interfaceState.search {
|
||||
@ -104,6 +105,7 @@ extension ChatControllerImpl {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.searchResultsCount.set(results.totalCount)
|
||||
var navigateIndex: MessageIndex?
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||
if let data = current.search {
|
||||
@ -152,6 +154,7 @@ extension ChatControllerImpl {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.searchResultsCount.set(results.totalCount)
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
|
||||
if let data = current.search, let previousResultsState = data.resultsState {
|
||||
let messageIndices = results.messages.map({ $0.index }).sorted()
|
||||
@ -167,11 +170,13 @@ extension ChatControllerImpl {
|
||||
}))
|
||||
} else {
|
||||
self.searching.set(false)
|
||||
self.searchResultsCount.set(0)
|
||||
self.searchDisposable?.set(nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.searching.set(false)
|
||||
self.searchResultsCount.set(0)
|
||||
self.searchDisposable?.set(nil)
|
||||
|
||||
if let data = interfaceState.search {
|
||||
|
@ -19,34 +19,6 @@ import AnimatedTextComponent
|
||||
|
||||
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 {
|
||||
private struct Params: Equatable {
|
||||
var width: CGFloat
|
||||
@ -100,6 +72,14 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
private var totalMessageCount: Int?
|
||||
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? {
|
||||
didSet {
|
||||
}
|
||||
@ -223,7 +203,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
var canChangeListMode = false
|
||||
|
||||
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)
|
||||
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
||||
canChangeListMode = true
|
||||
@ -237,7 +225,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string)
|
||||
)]
|
||||
} 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: [
|
||||
@ -247,7 +235,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
} else {
|
||||
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: [
|
||||
@ -263,7 +251,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
} else if let count = self.tagMessageCount?.count ?? self.totalMessageCount {
|
||||
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: [
|
||||
@ -282,7 +270,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
|
||||
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)
|
||||
])
|
||||
|
||||
@ -346,7 +334,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
var nextLeftX: CGFloat = 16.0
|
||||
|
||||
if !self.alwaysShowTotalMessagesCount {
|
||||
if !self.alwaysShowTotalMessagesCount && self.externalSearchResultsCount == nil {
|
||||
nextLeftX = 12.0
|
||||
let calendarButtonSize = self.calendarButton.update(
|
||||
transition: .immediate,
|
||||
@ -372,12 +360,28 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
||||
if let calendarButtonView = self.calendarButton.view {
|
||||
if calendarButtonView.superview == nil {
|
||||
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)
|
||||
}
|
||||
nextLeftX += calendarButtonSize.width + 8.0
|
||||
} 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 {
|
||||
|
@ -1924,8 +1924,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return inputPanelNode
|
||||
}
|
||||
|
||||
public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController {
|
||||
return HashtagSearchController(context: context, peer: peer, query: query, mode: all ? .noChat : .generic)
|
||||
public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController {
|
||||
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 {
|
||||
|
@ -9,23 +9,27 @@ public struct ChatTranslationState: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case baseLang
|
||||
case fromLang
|
||||
case timestamp
|
||||
case toLang
|
||||
case isEnabled
|
||||
}
|
||||
|
||||
public let baseLang: String
|
||||
public let fromLang: String
|
||||
public let timestamp: Int32?
|
||||
public let toLang: String?
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(
|
||||
baseLang: String,
|
||||
fromLang: String,
|
||||
timestamp: Int32?,
|
||||
toLang: String?,
|
||||
isEnabled: Bool
|
||||
) {
|
||||
self.baseLang = baseLang
|
||||
self.fromLang = fromLang
|
||||
self.timestamp = timestamp
|
||||
self.toLang = toLang
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
@ -35,6 +39,7 @@ public struct ChatTranslationState: Codable {
|
||||
|
||||
self.baseLang = try container.decode(String.self, forKey: .baseLang)
|
||||
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.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.fromLang, forKey: .fromLang)
|
||||
try container.encodeIfPresent(self.timestamp, forKey: .timestamp)
|
||||
try container.encodeIfPresent(self.toLang, forKey: .toLang)
|
||||
try container.encode(self.isEnabled, forKey: .isEnabled)
|
||||
}
|
||||
@ -52,6 +58,7 @@ public struct ChatTranslationState: Codable {
|
||||
return ChatTranslationState(
|
||||
baseLang: self.baseLang,
|
||||
fromLang: self.fromLang,
|
||||
timestamp: self.timestamp,
|
||||
toLang: toLang,
|
||||
isEnabled: self.isEnabled
|
||||
)
|
||||
@ -61,6 +68,7 @@ public struct ChatTranslationState: Codable {
|
||||
return ChatTranslationState(
|
||||
baseLang: self.baseLang,
|
||||
fromLang: self.fromLang,
|
||||
timestamp: self.timestamp,
|
||||
toLang: self.toLang,
|
||||
isEnabled: isEnabled
|
||||
)
|
||||
@ -191,7 +199,8 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
||||
|
||||
return cachedChatTranslationState(engine: context.engine, peerId: peerId)
|
||||
|> 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) {
|
||||
return .single(cached)
|
||||
} else {
|
||||
@ -277,7 +286,13 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
||||
if loggingEnabled {
|
||||
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()
|
||||
if !dontTranslateLanguages.contains(fromLang) {
|
||||
return state
|
||||
|
@ -587,6 +587,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private var updateWebViewWhenStable = false
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
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)))
|
||||
if !webView.frame.width.isZero && webView.frame != frame {
|
||||
self.updateWebViewWhenStable = true
|
||||
}
|
||||
|
||||
var bottomInset = layout.intrinsicInsets.bottom + layout.additionalInsets.bottom
|
||||
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 {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user