diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index b604bd5ca8..198ed139c8 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -388,7 +388,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { updatedPlayerStatusSignal = videoNode.status |> mapToSignal { status -> Signal in if let status = status, case .buffering = status.status { - return .single(status) |> delay(1.0, queue: Queue.mainQueue()) + return .single(status) |> delay(0.5, queue: Queue.mainQueue()) } else { return .single(status) } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 7e464768b9..7d675dc900 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -1005,7 +1005,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } else if isSecretMedia, let secretProgressIcon = secretProgressIcon { state = .customIcon(secretProgressIcon) } else if let file = media as? TelegramMediaFile { - let isInlinePlayableVideo = file.isVideo && !isSecretMedia && automaticPlayback + let isInlinePlayableVideo = file.isVideo && !isSecretMedia && (self.automaticPlayback ?? false) if !isInlinePlayableVideo && file.isVideo { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } else { @@ -1069,11 +1069,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } if let statusNode = self.statusNode { - if state == .none { + var removeStatusNode = false + if statusNode.state != .none && state == .none { self.statusNode = nil + removeStatusNode = true } statusNode.transitionToState(state, completion: { [weak statusNode] in - if state == .none { + if removeStatusNode { statusNode?.removeFromSupernode() } }) @@ -1217,6 +1219,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { var actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd = .loopDisablingSound if let message = self.message, message.id.peerId.namespace == Namespaces.Peer.CloudChannel { actionAtEnd = .loop + } else { + actionAtEnd = .repeatIfNeeded } if let videoNode = self.videoNode, let context = self.context, (self.automaticPlayback ?? false) && !isAnimated { diff --git a/TelegramUI/GifPaneSearchContentNode.swift b/TelegramUI/GifPaneSearchContentNode.swift index 8ec3ca3554..856b4fd89a 100644 --- a/TelegramUI/GifPaneSearchContentNode.swift +++ b/TelegramUI/GifPaneSearchContentNode.swift @@ -72,7 +72,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode { self.searchDisposable.dispose() } - func updateText(_ text: String) { + func updateText(_ text: String, languageCode: String?) { let signal: Signal<[FileMediaReference]?, NoError> if !text.isEmpty { signal = self.signalForQuery(text) @@ -133,7 +133,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode { } if firstLayout { - self.updateText("") + self.updateText("", languageCode: nil) } } diff --git a/TelegramUI/MediaPlayer.swift b/TelegramUI/MediaPlayer.swift index 52a1b23467..6ca422717c 100644 --- a/TelegramUI/MediaPlayer.swift +++ b/TelegramUI/MediaPlayer.swift @@ -52,6 +52,7 @@ enum MediaPlayerPlayOnceWithSoundActionAtEnd { case loop case loopDisablingSound case stop + case repeatIfNeeded } enum MediaPlayerPlayOnceWithSoundSeek { diff --git a/TelegramUI/NativeVideoContent.swift b/TelegramUI/NativeVideoContent.swift index 52809eb558..eddc780d5a 100644 --- a/TelegramUI/NativeVideoContent.swift +++ b/TelegramUI/NativeVideoContent.swift @@ -275,7 +275,27 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.player.actionAtEnd = .loopDisablingSound(action) case .stop: self.player.actionAtEnd = .action(action) + case .repeatIfNeeded: + let _ = (self.player.status + |> deliverOnMainQueue + |> take(1)).start(next: { [weak self] status in + guard let strongSelf = self else { + return + } + if status.timestamp > status.duration * 0.1 { + strongSelf.player.actionAtEnd = .action({ [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.player.seek(timestamp: 0.0) + strongSelf.player.actionAtEnd = .loopDisablingSound(action) + }) + } else { + strongSelf.player.actionAtEnd = .loopDisablingSound(action) + } + }) } + self.player.playOnceWithSound(playAndRecord: playAndRecord, seekToStart: seekToStart) } @@ -298,7 +318,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent switch actionAtEnd { case .loop: self.player.actionAtEnd = .loop({}) - case .loopDisablingSound: + case .loopDisablingSound, .repeatIfNeeded: self.player.actionAtEnd = .loopDisablingSound(action) case .stop: self.player.actionAtEnd = .action(action) diff --git a/TelegramUI/PaneSearchBarNode.swift b/TelegramUI/PaneSearchBarNode.swift index f50e2bc261..54ae54e2f4 100644 --- a/TelegramUI/PaneSearchBarNode.swift +++ b/TelegramUI/PaneSearchBarNode.swift @@ -121,7 +121,7 @@ private class PaneSearchBarTextField: UITextField { class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate { var cancel: (() -> Void)? - var textUpdated: ((String) -> Void)? + var textUpdated: ((String, String) -> Void)? var clearPrefix: (() -> Void)? private let backgroundNode: ASDisplayNode @@ -446,7 +446,7 @@ class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate { @objc func textFieldDidChange(_ textField: UITextField) { self.updateIsEmpty() if let textUpdated = self.textUpdated { - textUpdated(textField.text ?? "") + textUpdated(textField.text ?? "", self.textField.textInputMode?.primaryLanguage ?? "") } } diff --git a/TelegramUI/PaneSearchContainerNode.swift b/TelegramUI/PaneSearchContainerNode.swift index 1e6577b8f3..65706911f1 100644 --- a/TelegramUI/PaneSearchContainerNode.swift +++ b/TelegramUI/PaneSearchContainerNode.swift @@ -13,7 +13,7 @@ protocol PaneSearchContentNode { var updateActivity: ((Bool) -> Void)? { get set } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) - func updateText(_ text: String) + func updateText(_ text: String, languageCode: String?) func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) @@ -74,8 +74,8 @@ final class PaneSearchContainerNode: ASDisplayNode { } self.searchBar.activate() - self.searchBar.textUpdated = { [weak self] text in - self?.contentNode.updateText(text) + self.searchBar.textUpdated = { [weak self] text, languageCode in + self?.contentNode.updateText(text, languageCode: languageCode) } self.updateThemeAndStrings(theme: theme, strings: strings) diff --git a/TelegramUI/RadialStatusNode.swift b/TelegramUI/RadialStatusNode.swift index 545e4bc03f..62882a12cd 100644 --- a/TelegramUI/RadialStatusNode.swift +++ b/TelegramUI/RadialStatusNode.swift @@ -207,9 +207,14 @@ public final class RadialStatusNode: ASControlNode { contentNode.frame = self.bounds contentNode.prepareAnimateIn(from: nil) self.addSubnode(contentNode) - if animated, case .check = state, self.isNodeLoaded { - contentNode.layout() - contentNode.animateIn(from: fromState, delay: 0.0) + if animated, self.isNodeLoaded { + switch state { + case .check, .progress: + contentNode.layout() + contentNode.animateIn(from: fromState, delay: 0.0) + default: + break + } } } self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion) diff --git a/TelegramUI/StickerPaneSearchContentNode.swift b/TelegramUI/StickerPaneSearchContentNode.swift index 2332d966bf..bdc640012b 100644 --- a/TelegramUI/StickerPaneSearchContentNode.swift +++ b/TelegramUI/StickerPaneSearchContentNode.swift @@ -151,6 +151,11 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { private var enqueuedTransitions: [StickerPaneSearchGridTransition] = [] + private let languageCodePromise = ValuePromise(ignoreRepeated: true) + private let emojiKeywordsPromise = Promise() + + private var languageCodeDisposable: Disposable? + private let searchDisposable = MetaDisposable() private let queue = Queue() @@ -250,34 +255,60 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { self._ready.set(self.trendingPane.ready) self.trendingPane.activate() + self.languageCodeDisposable = (self.languageCodePromise.get() + |> deliverOnMainQueue).start(next: { [weak self] languageCode in + if let strongSelf = self { + strongSelf.emojiKeywordsPromise.set(emojiKeywords(accountManager: strongSelf.context.sharedContext.accountManager, network: strongSelf.context.account.network, inputLanguageCode: languageCode)) + } + }) + self.updateThemeAndStrings(theme: theme, strings: strings) } deinit { + self.languageCodeDisposable?.dispose() self.searchDisposable.dispose() } - func updateText(_ text: String) { + func updateText(_ text: String, languageCode: String?) { let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError> if !text.isEmpty { + if let languageCode = languageCode, !languageCode.isEmpty { + self.languageCodePromise.set(languageCode) + } + let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in - var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] + var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) - if text.trimmingCharacters(in: .whitespacesAndNewlines).isSingleEmoji { - signals.append(searchStickers(account: self.context.account, query: text.firstEmoji) + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isSingleEmoji { + signals = .single([searchStickers(account: self.context.account, query: text.firstEmoji) |> take(1) - |> map { (nil, $0) }) - } else { - for entry in TGEmojiSuggestions.suggestions(forQuery: text.lowercased()) { - if let entry = entry as? TGAlphacodeEntry { - signals.append(searchStickers(account: self.context.account, query: entry.emoji) - |> take(1) - |> map { (entry.emoji, $0) }) + |> map { (nil, $0) }]) + } else if trimmed.count > 1 { + signals = self.emojiKeywordsPromise.get() + |> mapToSignal { keywords in + if let keywords = keywords { + return searchEmojiKeywords(keywords: keywords, query: trimmed.lowercased(), completeMatch: true) + |> map { result -> [Signal<(String?, [FoundStickerItem]), NoError>] in + var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] + for emoji in result { + signals.append(searchStickers(account: self.context.account, query: emoji) + |> take(1) + |> map { (emoji, $0) }) + } + return signals + } + } else { + return .complete() } } } - return combineLatest(signals).start(next: { results in + return (signals + |> mapToSignal { signals in + return combineLatest(signals) + }).start(next: { results in var result: [(String?, FoundStickerItem)] = [] for (emoji, stickers) in results { for sticker in stickers {