mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Search progress
This commit is contained in:
parent
4609546c49
commit
16048d4d77
@ -1282,7 +1282,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
||||
|
||||
#if DEBUG
|
||||
//debugSaveState(basePath: basePath + "/db", name: "previous2")
|
||||
debugRestoreState(basePath: basePath + "/db", name: "previous2")
|
||||
//debugRestoreState(basePath: basePath + "/db", name: "previous2")
|
||||
#endif
|
||||
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
|
@ -177,7 +177,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchCategories: nil,
|
||||
searchInitiallyHidden: true
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty
|
||||
)
|
||||
))
|
||||
|
||||
@ -244,11 +245,38 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
private var inputDataDisposable: Disposable?
|
||||
private var hasRecentGifsDisposable: Disposable?
|
||||
|
||||
private struct EmojiSearchResult {
|
||||
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||
var id: AnyHashable
|
||||
var version: Int
|
||||
var isPreset: Bool
|
||||
}
|
||||
|
||||
private struct EmojiSearchState {
|
||||
var result: EmojiSearchResult?
|
||||
var isSearching: Bool
|
||||
|
||||
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||
self.result = result
|
||||
self.isSearching = isSearching
|
||||
}
|
||||
}
|
||||
|
||||
private let emojiSearchDisposable = MetaDisposable()
|
||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable, version: Int)?>(nil)
|
||||
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||
private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) {
|
||||
didSet {
|
||||
self.emojiSearchState.set(.single(self.emojiSearchStateValue))
|
||||
}
|
||||
}
|
||||
|
||||
private let stickerSearchDisposable = MetaDisposable()
|
||||
private let stickerSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable, version: Int)?>(nil)
|
||||
private let stickerSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||
private var stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false) {
|
||||
didSet {
|
||||
self.stickerSearchState.set(.single(self.stickerSearchStateValue))
|
||||
}
|
||||
}
|
||||
|
||||
private let controllerInteraction: ChatControllerInteraction?
|
||||
|
||||
@ -356,7 +384,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -388,7 +417,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -426,7 +456,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .active
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -511,7 +542,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .active
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -950,13 +982,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
switch query {
|
||||
case .none:
|
||||
strongSelf.emojiSearchDisposable.set(nil)
|
||||
strongSelf.emojiSearchResult.set(.single(nil))
|
||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
||||
case let .text(rawQuery, languageCode):
|
||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if query.isEmpty {
|
||||
strongSelf.emojiSearchDisposable.set(nil)
|
||||
strongSelf.emojiSearchResult.set(.single(nil))
|
||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
||||
} else {
|
||||
let context = strongSelf.context
|
||||
|
||||
@ -1080,13 +1112,15 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
var version = 0
|
||||
strongSelf.emojiSearchStateValue.isSearching = true
|
||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
||||
|> delay(0.15, queue: .mainQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query), version)))
|
||||
|
||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||
version += 1
|
||||
}))
|
||||
}
|
||||
@ -1128,14 +1162,23 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
items: items
|
||||
)])
|
||||
}
|
||||
|
||||
let delayValue: Double
|
||||
/*#if DEBUG
|
||||
delayValue = 2.3
|
||||
#else*/
|
||||
delayValue = 0.0
|
||||
//#endif
|
||||
|
||||
var version = 0
|
||||
strongSelf.emojiSearchStateValue.isSearching = true
|
||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
||||
|> delay(delayValue, queue: .mainQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(value), version)))
|
||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||
version += 1
|
||||
}))
|
||||
}
|
||||
@ -1352,10 +1395,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
switch query {
|
||||
case .none:
|
||||
strongSelf.stickerSearchDisposable.set(nil)
|
||||
strongSelf.stickerSearchResult.set(.single(nil))
|
||||
strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
||||
case .text:
|
||||
strongSelf.stickerSearchDisposable.set(nil)
|
||||
strongSelf.stickerSearchResult.set(.single(nil))
|
||||
strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
||||
case let .category(value):
|
||||
let resultSignal = strongSelf.context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote])
|
||||
|> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||
@ -1406,9 +1449,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return
|
||||
}
|
||||
if group.items.isEmpty && !result.isFinalResult {
|
||||
strongSelf.stickerSearchStateValue.isSearching = true
|
||||
return
|
||||
}
|
||||
strongSelf.stickerSearchResult.set(.single((result.items, AnyHashable(value), version)))
|
||||
strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||
version += 1
|
||||
}))
|
||||
}
|
||||
@ -1428,10 +1472,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
self.inputDataDisposable = (combineLatest(queue: .mainQueue(),
|
||||
updatedInputData,
|
||||
self.gifComponent.get(),
|
||||
self.emojiSearchResult.get(),
|
||||
self.stickerSearchResult.get()
|
||||
self.emojiSearchState.get(),
|
||||
self.stickerSearchState.get()
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs, emojiSearchResult, stickerSearchResult in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs, emojiSearchState, stickerSearchState in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1440,7 +1484,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
if let emojiSearchResult = emojiSearchResult {
|
||||
if let emojiSearchResult = emojiSearchState.result {
|
||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
@ -1449,11 +1493,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
)
|
||||
}
|
||||
if let emoji = inputData.emoji {
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: .active)
|
||||
let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState)
|
||||
}
|
||||
} else if emojiSearchState.isSearching {
|
||||
if let emoji = inputData.emoji {
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching)
|
||||
}
|
||||
}
|
||||
|
||||
if let stickerSearchResult = stickerSearchResult {
|
||||
if let stickerSearchResult = stickerSearchState.result {
|
||||
var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||
//TODO:localize
|
||||
@ -1463,7 +1512,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
)
|
||||
}
|
||||
if let stickers = inputData.stickers {
|
||||
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: .active)
|
||||
let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty
|
||||
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: stickerSearchState.isSearching ? .searching : defaultSearchState)
|
||||
}
|
||||
} else if stickerSearchState.isSearching {
|
||||
if let stickers = inputData.stickers {
|
||||
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickers.contentItemGroups, itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: stickers.emptySearchResults, searchState: .searching)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ swift_library(
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
"//submodules/GZip",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
"//submodules/lottie-ios:Lottie",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1532,6 +1532,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
var isActive: Bool
|
||||
var hasPresetSearch: Bool
|
||||
var textInputState: EmojiSearchSearchBarComponent.TextInputState
|
||||
var searchState: EmojiPagerContentComponent.SearchState
|
||||
var size: CGSize
|
||||
var canFocus: Bool
|
||||
var searchCategories: EmojiSearchCategories?
|
||||
@ -1561,6 +1562,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
if lhs.textInputState != rhs.textInputState {
|
||||
return false
|
||||
}
|
||||
if lhs.searchState != rhs.searchState {
|
||||
return false
|
||||
}
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
@ -1587,11 +1591,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let tintBackgroundLayer: SimpleLayer
|
||||
|
||||
private let searchIconView: UIImageView
|
||||
private let searchIconTintView: UIImageView
|
||||
|
||||
private let backIconView: UIImageView
|
||||
private let backIconTintView: UIImageView
|
||||
private let statusIcon = ComponentView<Empty>()
|
||||
|
||||
private let clearIconView: UIImageView
|
||||
private let clearIconTintView: UIImageView
|
||||
@ -1625,12 +1625,6 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.tintBackgroundLayer = SimpleLayer()
|
||||
|
||||
self.searchIconView = UIImageView()
|
||||
self.searchIconTintView = UIImageView()
|
||||
|
||||
self.backIconView = UIImageView()
|
||||
self.backIconTintView = UIImageView()
|
||||
|
||||
self.clearIconView = UIImageView()
|
||||
self.clearIconTintView = UIImageView()
|
||||
self.clearIconButton = HighlightableButton()
|
||||
@ -1647,12 +1641,6 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.layer.addSublayer(self.backgroundLayer)
|
||||
self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer)
|
||||
|
||||
self.addSubview(self.searchIconView)
|
||||
self.tintContainerView.addSubview(self.searchIconTintView)
|
||||
|
||||
self.addSubview(self.backIconView)
|
||||
self.tintContainerView.addSubview(self.backIconTintView)
|
||||
|
||||
self.addSubview(self.clearIconView)
|
||||
self.tintContainerView.addSubview(self.clearIconTintView)
|
||||
self.addSubview(self.clearIconButton)
|
||||
@ -1716,7 +1704,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let location = recognizer.location(in: self)
|
||||
if self.backIconView.frame.contains(location) {
|
||||
if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil {
|
||||
self.clearCategorySearch()
|
||||
} else {
|
||||
self.activateTextInput()
|
||||
@ -1838,10 +1826,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
return
|
||||
}
|
||||
self.params = nil
|
||||
self.update(context: params.context, theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, transition: transition)
|
||||
self.update(context: params.context, theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition)
|
||||
}
|
||||
|
||||
public func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, transition: Transition) {
|
||||
public func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) {
|
||||
let textInputState: EmojiSearchSearchBarComponent.TextInputState
|
||||
if let textField = self.textField {
|
||||
textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
|
||||
@ -1858,6 +1846,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
isActive: isActive,
|
||||
hasPresetSearch: self.currentPresetSearchTerm == nil,
|
||||
textInputState: textInputState,
|
||||
searchState: searchState,
|
||||
size: size,
|
||||
canFocus: canFocus,
|
||||
searchCategories: searchCategories
|
||||
@ -1870,7 +1859,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
let isActiveWithText = isActive && self.currentPresetSearchTerm == nil
|
||||
|
||||
if self.params?.theme !== theme {
|
||||
self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
/*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
|
||||
self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)
|
||||
@ -1878,7 +1867,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
|
||||
self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)
|
||||
self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/
|
||||
|
||||
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
@ -1888,11 +1877,11 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
|
||||
self.params = params
|
||||
|
||||
let sideInset: CGFloat = 8.0
|
||||
let sideInset: CGFloat = 12.0
|
||||
let topInset: CGFloat = 8.0
|
||||
let inputHeight: CGFloat = 36.0
|
||||
|
||||
let sideTextInset: CGFloat = 8.0 + 4.0 + 24.0
|
||||
let sideTextInset: CGFloat = sideInset + 4.0 + 24.0
|
||||
|
||||
if useOpaqueTheme {
|
||||
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
||||
@ -1941,7 +1930,40 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
|
||||
self.textFrame = textFrame
|
||||
|
||||
if let image = self.searchIconView.image {
|
||||
let statusContent: EmojiSearchStatusComponent.Content
|
||||
switch searchState {
|
||||
case .empty:
|
||||
statusContent = .search
|
||||
case .searching:
|
||||
statusContent = .progress
|
||||
case .active:
|
||||
statusContent = .results
|
||||
}
|
||||
|
||||
let statusSize = CGSize(width: 24.0, height: 24.0)
|
||||
let _ = self.statusIcon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(EmojiSearchStatusComponent(
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
content: statusContent
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: statusSize
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize)
|
||||
if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View {
|
||||
if statusIconView.superview == nil {
|
||||
self.addSubview(statusIconView)
|
||||
self.tintContainerView.addSubview(statusIconView.tintContainerView)
|
||||
}
|
||||
|
||||
transition.setFrame(view: statusIconView, frame: iconFrame)
|
||||
transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame)
|
||||
}
|
||||
|
||||
/*if let image = self.searchIconView.image {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||
transition.setBounds(view: self.searchIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
transition.setPosition(view: self.searchIconView, position: iconFrame.center)
|
||||
@ -1963,9 +1985,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
transition.setAlpha(view: self.backIconView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0)
|
||||
transition.setScale(view: self.backIconTintView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001)
|
||||
transition.setAlpha(view: self.backIconTintView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0)
|
||||
}
|
||||
}*/
|
||||
|
||||
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minX), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height))
|
||||
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height))
|
||||
let _ = self.placeholderContent.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(EmojiSearchSearchBarComponent(
|
||||
@ -6545,7 +6567,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, transition: transition)
|
||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
||||
if !useOpaqueTheme {
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
|
||||
transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in
|
||||
|
@ -162,7 +162,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
self.containerSize = containerSize
|
||||
self.itemCount = itemCount
|
||||
self.itemSpacing = 11.0
|
||||
self.leftInset = 6.0
|
||||
self.leftInset = 8.0
|
||||
self.rightInset = 8.0
|
||||
self.itemSize = CGSize(width: 24.0, height: 24.0)
|
||||
self.textSpacing = 11.0
|
||||
|
@ -0,0 +1,781 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import PagerComponent
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccountContext
|
||||
import AsyncDisplayKit
|
||||
import ComponentDisplayAdapters
|
||||
import LottieAnimationComponent
|
||||
import EmojiStatusComponent
|
||||
import LottieComponent
|
||||
import AudioToolbox
|
||||
import SwiftSignalKit
|
||||
import GZip
|
||||
import RLottieBinding
|
||||
import AppBundle
|
||||
import Lottie
|
||||
|
||||
private final class LottieDirectContent: LottieComponent.Content {
|
||||
let path: String
|
||||
|
||||
init(path: String) {
|
||||
self.path = path
|
||||
}
|
||||
|
||||
override func isEqual(to other: LottieComponent.Content) -> Bool {
|
||||
guard let other = other as? LottieDirectContent else {
|
||||
return false
|
||||
}
|
||||
if self.path != other.path {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override func load(_ f: @escaping (Data) -> Void) -> Disposable {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.path)) {
|
||||
let result = TGGUnzipData(data, 2 * 1024 * 1024) ?? data
|
||||
f(result)
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private protocol EmojiSearchStatusAnimationState {
|
||||
var content: EmojiSearchStatusComponent.ContentState { get }
|
||||
var image: UIImage? { get }
|
||||
var isCompleted: Bool { get }
|
||||
|
||||
func advanceIfNeeded()
|
||||
func updateImage()
|
||||
}
|
||||
|
||||
final class EmojiSearchStatusComponent: Component {
|
||||
enum Content: Equatable {
|
||||
case search
|
||||
case progress
|
||||
case results
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let useOpaqueTheme: Bool
|
||||
let content: Content
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
useOpaqueTheme: Bool,
|
||||
content: Content
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.useOpaqueTheme = useOpaqueTheme
|
||||
self.content = content
|
||||
}
|
||||
|
||||
static func ==(lhs: EmojiSearchStatusComponent, rhs: EmojiSearchStatusComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.useOpaqueTheme != rhs.useOpaqueTheme {
|
||||
return false
|
||||
}
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate enum ContentState {
|
||||
case search
|
||||
case searchToProgress
|
||||
case progress
|
||||
case results
|
||||
|
||||
init(content: Content) {
|
||||
switch content {
|
||||
case .search:
|
||||
self = .search
|
||||
case .progress:
|
||||
self = .progress
|
||||
case .results:
|
||||
self = .results
|
||||
}
|
||||
}
|
||||
|
||||
var content: Content {
|
||||
switch self {
|
||||
case .search:
|
||||
return .search
|
||||
case .searchToProgress, .progress:
|
||||
return .progress
|
||||
case .results:
|
||||
return .results
|
||||
}
|
||||
}
|
||||
|
||||
var automaticNextState: ContentState? {
|
||||
switch self {
|
||||
case .searchToProgress:
|
||||
return .progress
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LottieAnimationState: EmojiSearchStatusAnimationState {
|
||||
let content: ContentState
|
||||
|
||||
private let animationInstance: LottieInstance
|
||||
|
||||
private var currentFrameStartTime: Double?
|
||||
private var currentFrame: Int = 0
|
||||
private let frameRange: ClosedRange<Int>?
|
||||
private(set) var image: UIImage?
|
||||
|
||||
private(set) var previousAnimationState: EmojiSearchStatusAnimationState?
|
||||
|
||||
private(set) var isCompleted: Bool = false
|
||||
|
||||
var displaySize: CGSize {
|
||||
didSet {
|
||||
if self.displaySize != oldValue {
|
||||
self.image = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init?(content: ContentState, data: Data, displaySize: CGSize, frameRange: ClosedRange<Int>?, previousAnimationState: EmojiSearchStatusAnimationState?) {
|
||||
guard let animationInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||
return nil
|
||||
}
|
||||
self.content = content
|
||||
self.animationInstance = animationInstance
|
||||
self.displaySize = displaySize
|
||||
self.frameRange = frameRange
|
||||
self.previousAnimationState = previousAnimationState
|
||||
|
||||
if let frameRange {
|
||||
self.currentFrame = frameRange.lowerBound
|
||||
}
|
||||
}
|
||||
|
||||
func advanceIfNeeded() {
|
||||
if let previousAnimationState = self.previousAnimationState {
|
||||
previousAnimationState.advanceIfNeeded()
|
||||
if previousAnimationState.isCompleted {
|
||||
self.previousAnimationState = nil
|
||||
}
|
||||
if previousAnimationState.image == nil {
|
||||
self.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
if self.isCompleted {
|
||||
return
|
||||
}
|
||||
|
||||
if let frameRange = self.frameRange {
|
||||
if frameRange.lowerBound == frameRange.upperBound {
|
||||
self.isCompleted = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
guard let currentFrameStartTime = self.currentFrameStartTime else {
|
||||
currentFrameStartTime = timestamp
|
||||
return
|
||||
}
|
||||
|
||||
let secondsPerFrame: Double
|
||||
if animationInstance.frameRate == 0 {
|
||||
secondsPerFrame = 1.0 / 60.0
|
||||
} else {
|
||||
secondsPerFrame = 1.0 / Double(animationInstance.frameRate)
|
||||
}
|
||||
|
||||
if currentFrameStartTime + secondsPerFrame * 0.9 <= timestamp {
|
||||
self.currentFrame += 1
|
||||
let maxFrame: Int
|
||||
if let frameRange = self.frameRange {
|
||||
maxFrame = frameRange.upperBound
|
||||
} else {
|
||||
maxFrame = Int(animationInstance.frameCount) - 1
|
||||
}
|
||||
if self.currentFrame >= maxFrame {
|
||||
self.currentFrame = maxFrame
|
||||
self.isCompleted = true
|
||||
} else {
|
||||
self.currentFrameStartTime = timestamp
|
||||
self.image = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateImage() {
|
||||
guard let frameContext = DrawingContext(size: self.displaySize, scale: 1.0, opaque: false, clear: true) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.animationInstance.renderFrame(with: Int32(self.currentFrame % Int(self.animationInstance.frameCount)), into: frameContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(self.displaySize.width), height: Int32(self.displaySize.height), bytesPerRow: Int32(frameContext.bytesPerRow))
|
||||
|
||||
if let previousAnimationState = self.previousAnimationState as? ProgressAnimationState {
|
||||
guard let context = DrawingContext(size: self.displaySize, scale: 1.0, opaque: false, clear: true) else {
|
||||
return
|
||||
}
|
||||
|
||||
if previousAnimationState.image == nil {
|
||||
previousAnimationState.updateImage()
|
||||
}
|
||||
if let frameImage = frameContext.generateImage()?.cgImage, let cgImage = previousAnimationState.image?.cgImage {
|
||||
context.withFlippedContext { c in
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(), size: context.size))
|
||||
|
||||
c.translateBy(x: self.displaySize.width * 0.5, y: self.displaySize.height * 0.5)
|
||||
c.rotate(by: previousAnimationState.currentRotationAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2.0))
|
||||
c.translateBy(x: -self.displaySize.width * 0.5, y: -self.displaySize.height * 0.5)
|
||||
|
||||
c.draw(frameImage, in: CGRect(origin: CGPoint(), size: context.size))
|
||||
}
|
||||
}
|
||||
|
||||
self.image = context.generateImage()?.withRenderingMode(.alwaysTemplate)
|
||||
} else {
|
||||
self.image = frameContext.generateImage()?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProgressAnimationState: EmojiSearchStatusAnimationState {
|
||||
let content: ContentState
|
||||
|
||||
private var currentFrameStartTime: Double?
|
||||
private var currentOffset: CGFloat
|
||||
private(set) var currentRotationAngle: CGFloat
|
||||
|
||||
private var lastStageStartOffset: CGFloat?
|
||||
private var lastStageRotationAngle: CGFloat?
|
||||
|
||||
private(set) var image: UIImage?
|
||||
|
||||
var shouldComplete: Bool = false {
|
||||
didSet {
|
||||
if self.shouldComplete != oldValue && self.shouldComplete {
|
||||
self.lastStageStartOffset = self.currentOffset
|
||||
self.currentRotationAngle = self.currentRotationAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2.0)
|
||||
self.lastStageRotationAngle = self.currentRotationAngle
|
||||
}
|
||||
}
|
||||
}
|
||||
private(set) var isCompleted: Bool = false
|
||||
|
||||
var displaySize: CGSize {
|
||||
didSet {
|
||||
if self.displaySize != oldValue {
|
||||
self.image = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(content: ContentState, displaySize: CGSize) {
|
||||
self.content = content
|
||||
self.displaySize = displaySize
|
||||
self.currentOffset = 0.0
|
||||
self.currentRotationAngle = 0.0
|
||||
}
|
||||
|
||||
func advanceIfNeeded() {
|
||||
if self.isCompleted {
|
||||
return
|
||||
}
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
guard let currentFrameStartTime = self.currentFrameStartTime else {
|
||||
currentFrameStartTime = timestamp
|
||||
return
|
||||
}
|
||||
|
||||
let secondsPerFrame: Double = 1.0 / 60.0
|
||||
let offsetVelocity: CGFloat = CGFloat.pi * 3.0
|
||||
let maxOffset: CGFloat = CGFloat.pi * 2.0 - CGFloat.pi * 1.0 / 1.4
|
||||
|
||||
let rotationVelocity: CGFloat = CGFloat.pi * 3.0 * 1.0
|
||||
|
||||
if currentFrameStartTime + secondsPerFrame * 0.9 <= timestamp {
|
||||
if let lastStageStartOffset = self.lastStageStartOffset {
|
||||
let lastStageRemainingOffset: CGFloat = CGFloat.pi * 2.0 - lastStageStartOffset
|
||||
let lastStageRemainingVelocity: CGFloat = lastStageRemainingOffset / 9.0 * 60.0
|
||||
self.currentOffset = min(CGFloat.pi * 2.0, self.currentOffset + lastStageRemainingVelocity * secondsPerFrame)
|
||||
} else if self.shouldComplete {
|
||||
self.currentOffset = min(CGFloat.pi * 2.0, self.currentOffset + offsetVelocity * secondsPerFrame)
|
||||
if self.currentOffset == CGFloat.pi * 2.0 {
|
||||
self.isCompleted = true
|
||||
}
|
||||
} else {
|
||||
self.currentOffset = min(maxOffset, self.currentOffset + offsetVelocity * secondsPerFrame)
|
||||
}
|
||||
if let lastStageRotationAngle = self.lastStageRotationAngle {
|
||||
let _ = lastStageRotationAngle
|
||||
/*let lastStageRemainingAngle: CGFloat = CGFloat.pi * 2.0 + lastStageRotationAngle
|
||||
let lastStageRemainingAngleVelocity: CGFloat = lastStageRemainingAngle / 12.0 * 60.0
|
||||
self.currentRotationAngle = max(-CGFloat.pi * 2.0, self.currentRotationAngle - lastStageRemainingAngleVelocity * secondsPerFrame)*/
|
||||
self.currentRotationAngle = max(-CGFloat.pi * 2.0, self.currentRotationAngle - rotationVelocity * secondsPerFrame)
|
||||
} else {
|
||||
self.currentRotationAngle -= rotationVelocity * secondsPerFrame
|
||||
}
|
||||
|
||||
if self.lastStageStartOffset != nil && self.lastStageRotationAngle != nil {
|
||||
if self.currentOffset == CGFloat.pi * 2.0 && self.currentRotationAngle == -CGFloat.pi * 2.0 {
|
||||
self.isCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
self.currentFrameStartTime = timestamp
|
||||
self.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateImage() {
|
||||
guard let context = DrawingContext(size: self.displaySize, scale: 1.0, opaque: false, clear: true) else {
|
||||
return
|
||||
}
|
||||
|
||||
context.withFlippedContext { c in
|
||||
c.setStrokeColor(UIColor.white.cgColor)
|
||||
c.setLineCap(.round)
|
||||
|
||||
let lineWidth: CGFloat = 1.33 * UIScreenScale
|
||||
let fullDiameter = 20.0 * UIScreenScale
|
||||
|
||||
c.setLineWidth(lineWidth)
|
||||
|
||||
let startAngle: CGFloat = 0.0
|
||||
let endAngle: CGFloat = startAngle + (CGFloat.pi * 2.0 - self.currentOffset.truncatingRemainder(dividingBy: CGFloat.pi * 2.0))
|
||||
|
||||
c.translateBy(x: self.displaySize.width * 0.5, y: self.displaySize.height * 0.5)
|
||||
c.rotate(by: self.currentRotationAngle.truncatingRemainder(dividingBy: CGFloat.pi * 2.0))
|
||||
c.translateBy(x: -self.displaySize.width * 0.5, y: -self.displaySize.height * 0.5)
|
||||
|
||||
if self.currentOffset != CGFloat.pi * 2.0 {
|
||||
c.addArc(center: CGPoint(x: self.displaySize.width * 0.5, y: self.displaySize.height * 0.5), radius: fullDiameter * 0.5 - lineWidth, startAngle: startAngle, endAngle: endAngle, clockwise: false)
|
||||
c.strokePath()
|
||||
}
|
||||
}
|
||||
self.image = context.generateImage()?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: EmojiSearchStatusComponent?
|
||||
|
||||
private var disappearingAnimationStates: [(UIImageView, UIImageView, EmojiSearchStatusAnimationState)] = []
|
||||
|
||||
private var currentAnimationState: EmojiSearchStatusAnimationState?
|
||||
private var pendingContent: Content?
|
||||
|
||||
private var displaySize: CGSize?
|
||||
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||
|
||||
public let contentView: UIImageView
|
||||
public let tintContainerView: UIView
|
||||
public let tintContentView: UIImageView
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.contentView = UIImageView()
|
||||
self.tintContainerView = UIView()
|
||||
self.tintContentView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
|
||||
self.tintContainerView.isUserInteractionEnabled = false
|
||||
self.tintContainerView.addSubview(self.tintContentView)
|
||||
|
||||
//self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EmojiSearchStatusComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale)
|
||||
self.displaySize = displaySize
|
||||
|
||||
let overlayColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
let baseColor: UIColor = .white
|
||||
|
||||
if self.contentView.tintColor != overlayColor {
|
||||
self.contentView.tintColor = overlayColor
|
||||
}
|
||||
if self.tintContentView.tintColor != baseColor {
|
||||
self.tintContentView.tintColor = baseColor
|
||||
}
|
||||
|
||||
let currentTargetContent = self.pendingContent ?? self.currentAnimationState?.content.content
|
||||
if component.content != currentTargetContent {
|
||||
var canSwitchNow = false
|
||||
if let currentAnimationState = self.currentAnimationState {
|
||||
if currentAnimationState.isCompleted {
|
||||
canSwitchNow = true
|
||||
} else if let _ = currentAnimationState as? ProgressAnimationState {
|
||||
canSwitchNow = true
|
||||
}
|
||||
} else {
|
||||
canSwitchNow = true
|
||||
}
|
||||
|
||||
if canSwitchNow {
|
||||
/*if let currentAnimationState = self.currentAnimationState, case .search = currentAnimationState.content, case .progress = component.content {
|
||||
self.switchToContent(content: .searchToProgress)
|
||||
} else {*/
|
||||
self.switchToContent(content: ContentState(content: component.content))
|
||||
//}
|
||||
} else {
|
||||
self.pendingContent = component.content
|
||||
}
|
||||
}
|
||||
|
||||
self.updateAnimation()
|
||||
|
||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setFrame(view: self.tintContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
private func switchToContent(content: ContentState) {
|
||||
guard let displaySize = self.displaySize else {
|
||||
return
|
||||
}
|
||||
|
||||
enum FrameRangeValue {
|
||||
case index(Int)
|
||||
case marker(String)
|
||||
case end
|
||||
}
|
||||
|
||||
var name: String?
|
||||
var isJson = false
|
||||
var frameRange: (FrameRangeValue, FrameRangeValue)?
|
||||
var manualTransition = false
|
||||
var previousAnimationState: EmojiSearchStatusAnimationState?
|
||||
previousAnimationState = nil
|
||||
|
||||
let manualPreviousState = self.currentAnimationState
|
||||
|
||||
if let currentAnimationState = self.currentAnimationState {
|
||||
switch currentAnimationState.content {
|
||||
case .search:
|
||||
switch content {
|
||||
case .search:
|
||||
name = "emoji_search_to_arrow"
|
||||
frameRange = (.index(0), .index(0))
|
||||
case .searchToProgress:
|
||||
name = "emoji_search_to_progress"
|
||||
isJson = true
|
||||
//frameRange = (.index(0), .marker("{\r\"name\":\"Search to Progress\"\r}"))
|
||||
frameRange = (.index(0), .index(7))
|
||||
case .progress:
|
||||
manualTransition = true
|
||||
break
|
||||
case .results:
|
||||
name = "emoji_search_to_arrow"
|
||||
}
|
||||
case .searchToProgress:
|
||||
switch content {
|
||||
case .search:
|
||||
manualTransition = true
|
||||
name = "emoji_search_to_arrow"
|
||||
frameRange = (.index(0), .index(0))
|
||||
case .searchToProgress:
|
||||
break
|
||||
case .progress:
|
||||
break
|
||||
case .results:
|
||||
manualTransition = true
|
||||
name = "emoji_arrow_to_search"
|
||||
frameRange = (.index(0), .index(0))
|
||||
}
|
||||
case .progress:
|
||||
switch content {
|
||||
case .search:
|
||||
manualTransition = true
|
||||
name = "emoji_search_to_arrow"
|
||||
frameRange = (.index(0), .index(0))
|
||||
case .searchToProgress:
|
||||
break
|
||||
case .progress:
|
||||
break
|
||||
case .results:
|
||||
manualTransition = true
|
||||
name = "emoji_arrow_to_search"
|
||||
frameRange = (.index(0), .index(0))
|
||||
}
|
||||
/*switch content {
|
||||
case .search:
|
||||
manualTransition = true
|
||||
name = "emoji_search_to_arrow"
|
||||
frameRange = (.index(0), .index(0))
|
||||
case .searchToProgress:
|
||||
name = "emoji_search_to_progress"
|
||||
isJson = true
|
||||
case .progress:
|
||||
break
|
||||
case .results:
|
||||
name = "emoji_search_to_progress"
|
||||
isJson = true
|
||||
//frameRange = (.marker("{\n\"name\":\"Progress to Arrow\"\n}"), .end)
|
||||
frameRange = (.index(87), .end)
|
||||
|
||||
previousAnimationState = currentAnimationState
|
||||
(currentAnimationState as? ProgressAnimationState)?.shouldComplete = true
|
||||
|
||||
/*name = "emoji_arrow_to_search"
|
||||
frameRange = (.index(0), .index(0))*/
|
||||
}*/
|
||||
case .results:
|
||||
switch content {
|
||||
case .search:
|
||||
name = "emoji_arrow_to_search"
|
||||
case .searchToProgress:
|
||||
name = "emoji_search_to_progress"
|
||||
isJson = true
|
||||
case .progress:
|
||||
manualTransition = true
|
||||
case .results:
|
||||
name = "emoji_arrow_to_search"
|
||||
frameRange = (.index(0), .index(0))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch content {
|
||||
case .search:
|
||||
name = "emoji_search_to_arrow"
|
||||
frameRange = (.index(0), .index(0))
|
||||
case .searchToProgress:
|
||||
name = "emoji_search_to_progress"
|
||||
isJson = true
|
||||
case .progress:
|
||||
break
|
||||
case .results:
|
||||
name = "emoji_arrow_to_search"
|
||||
frameRange = (.index(0), .index(0))
|
||||
}
|
||||
}
|
||||
|
||||
if manualTransition, let manualPreviousState {
|
||||
let tempImageView = UIImageView()
|
||||
tempImageView.image = self.contentView.image
|
||||
tempImageView.frame = self.contentView.frame
|
||||
tempImageView.tintColor = self.contentView.tintColor
|
||||
self.contentView.superview?.insertSubview(tempImageView, aboveSubview: self.contentView)
|
||||
|
||||
let tempTintImageView = UIImageView()
|
||||
tempTintImageView.image = self.tintContentView.image
|
||||
tempTintImageView.frame = self.tintContentView.frame
|
||||
tempTintImageView.tintColor = self.tintContentView.tintColor
|
||||
self.tintContentView.superview?.insertSubview(tempTintImageView, aboveSubview: self.tintContentView)
|
||||
|
||||
self.disappearingAnimationStates.append((tempImageView, tempTintImageView, manualPreviousState))
|
||||
|
||||
let minScale: CGFloat = 0.6
|
||||
|
||||
tempImageView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak self, weak tempImageView] _ in
|
||||
if let self, let tempImageView {
|
||||
tempImageView.removeFromSuperview()
|
||||
self.disappearingAnimationStates.removeAll(where: { $0.0 === tempImageView })
|
||||
}
|
||||
})
|
||||
tempImageView.layer.animateScale(from: 1.0, to: minScale, duration: 0.18, removeOnCompletion: false)
|
||||
tempTintImageView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak self, weak tempTintImageView] _ in
|
||||
if let self, let tempTintImageView {
|
||||
tempImageView.removeFromSuperview()
|
||||
self.disappearingAnimationStates.removeAll(where: { $0.1 === tempTintImageView })
|
||||
}
|
||||
})
|
||||
tempTintImageView.layer.animateScale(from: 1.0, to: minScale, duration: 0.18, removeOnCompletion: false)
|
||||
|
||||
self.contentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
self.contentView.layer.animateScale(from: minScale, to: 1.0, duration: 0.18)
|
||||
self.tintContentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
self.tintContentView.layer.animateScale(from: minScale, to: 1.0, duration: 0.18)
|
||||
}
|
||||
|
||||
if case .progress = content {
|
||||
self.currentAnimationState = ProgressAnimationState(content: content, displaySize: displaySize)
|
||||
} else if let name, let data = getAppBundle().path(forResource: name, ofType: isJson ? "json" : "tgs").flatMap({
|
||||
return try? Data(contentsOf: URL(fileURLWithPath: $0))
|
||||
}).flatMap({ data -> Data in
|
||||
if isJson {
|
||||
return data
|
||||
}
|
||||
return TGGUnzipData(data, 2 * 1024 * 1024) ?? data
|
||||
}) {
|
||||
var resolvedFrameRange: ClosedRange<Int>?
|
||||
if let frameRange {
|
||||
var hasMarkers = false
|
||||
|
||||
if case .marker = frameRange.0 {
|
||||
hasMarkers = true
|
||||
}
|
||||
if case .marker = frameRange.1 {
|
||||
hasMarkers = true
|
||||
}
|
||||
if case .end = frameRange.0 {
|
||||
hasMarkers = true
|
||||
}
|
||||
if case .end = frameRange.1 {
|
||||
hasMarkers = true
|
||||
}
|
||||
|
||||
var resolvedLowerBound: Int = 0
|
||||
var resolvedUpperBound: Int = 0
|
||||
|
||||
if case let .index(index) = frameRange.0 {
|
||||
resolvedLowerBound = index
|
||||
}
|
||||
if case let .index(index) = frameRange.1 {
|
||||
resolvedUpperBound = index
|
||||
}
|
||||
|
||||
if hasMarkers, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let animation = try? Animation(dictionary: json) {
|
||||
let numFrames = animation.endFrame - animation.startFrame
|
||||
|
||||
if case let .marker(markerName) = frameRange.0 {
|
||||
if let value = animation.progressTime(forMarker: markerName) {
|
||||
resolvedLowerBound = Int(value * numFrames)
|
||||
}
|
||||
}
|
||||
if case .end = frameRange.0 {
|
||||
resolvedLowerBound = Int(numFrames) - 1
|
||||
}
|
||||
if case let .marker(markerName) = frameRange.1 {
|
||||
if let value = animation.progressTime(forMarker: markerName) {
|
||||
resolvedUpperBound = Int(round(value * numFrames))
|
||||
}
|
||||
}
|
||||
if case .end = frameRange.1 {
|
||||
resolvedUpperBound = Int(numFrames) - 1
|
||||
}
|
||||
}
|
||||
|
||||
resolvedFrameRange = resolvedLowerBound ... max(resolvedLowerBound, resolvedUpperBound)
|
||||
}
|
||||
|
||||
self.currentAnimationState = LottieAnimationState(content: content, data: data, displaySize: displaySize, frameRange: resolvedFrameRange, previousAnimationState: previousAnimationState)
|
||||
} else {
|
||||
self.currentAnimationState = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
var needsAnimation = false
|
||||
|
||||
for (tempView, tempTintView, animationState) in self.disappearingAnimationStates {
|
||||
animationState.advanceIfNeeded()
|
||||
if animationState.image == nil {
|
||||
animationState.updateImage()
|
||||
}
|
||||
tempView.image = animationState.image
|
||||
tempTintView.image = animationState.image
|
||||
|
||||
needsAnimation = true
|
||||
}
|
||||
|
||||
while true {
|
||||
if let currentAnimationState = self.currentAnimationState {
|
||||
if self.pendingContent != nil, let currentAnimationState = currentAnimationState as? ProgressAnimationState {
|
||||
currentAnimationState.shouldComplete = true
|
||||
}
|
||||
|
||||
currentAnimationState.advanceIfNeeded()
|
||||
|
||||
if currentAnimationState.image == nil {
|
||||
currentAnimationState.updateImage()
|
||||
}
|
||||
|
||||
if let previousAnimationState = (currentAnimationState as? LottieAnimationState)?.previousAnimationState, !previousAnimationState.isCompleted {
|
||||
needsAnimation = true
|
||||
}
|
||||
|
||||
if currentAnimationState.isCompleted {
|
||||
if self.pendingContent == nil, let automaticNextState = currentAnimationState.content.automaticNextState {
|
||||
self.switchToContent(content: automaticNextState)
|
||||
} else if let pendingContent = self.pendingContent {
|
||||
self.pendingContent = nil
|
||||
self.switchToContent(content: ContentState(content: pendingContent))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
needsAnimation = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let currentAnimationState = self.currentAnimationState {
|
||||
if currentAnimationState.image == nil {
|
||||
currentAnimationState.updateImage()
|
||||
}
|
||||
|
||||
if let image = currentAnimationState.image {
|
||||
self.contentView.image = image
|
||||
self.tintContentView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
if needsAnimation {
|
||||
if self.displayLink == nil {
|
||||
var counter = 0
|
||||
self.displayLink = SharedDisplayLinkDriver.shared.add(needsHighestFramerate: false, { [weak self] in
|
||||
counter += 1
|
||||
if counter % 1 == 0 {
|
||||
self?.updateAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let displayLink = self.displayLink {
|
||||
self.displayLink = nil
|
||||
displayLink.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -197,6 +197,7 @@ public final class GifPagerContentComponent: Component {
|
||||
public let displaySearchWithPlaceholder: String?
|
||||
public let searchCategories: EmojiSearchCategories?
|
||||
public let searchInitiallyHidden: Bool
|
||||
public let searchState: EmojiPagerContentComponent.SearchState
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -207,7 +208,8 @@ public final class GifPagerContentComponent: Component {
|
||||
loadMoreToken: String?,
|
||||
displaySearchWithPlaceholder: String?,
|
||||
searchCategories: EmojiSearchCategories?,
|
||||
searchInitiallyHidden: Bool
|
||||
searchInitiallyHidden: Bool,
|
||||
searchState: EmojiPagerContentComponent.SearchState
|
||||
) {
|
||||
self.context = context
|
||||
self.inputInteraction = inputInteraction
|
||||
@ -218,6 +220,7 @@ public final class GifPagerContentComponent: Component {
|
||||
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
|
||||
self.searchCategories = searchCategories
|
||||
self.searchInitiallyHidden = searchInitiallyHidden
|
||||
self.searchState = searchState
|
||||
}
|
||||
|
||||
public static func ==(lhs: GifPagerContentComponent, rhs: GifPagerContentComponent) -> Bool {
|
||||
@ -248,6 +251,9 @@ public final class GifPagerContentComponent: Component {
|
||||
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
|
||||
return false
|
||||
}
|
||||
if lhs.searchState != rhs.searchState {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1066,7 +1072,7 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
|
||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, transition: transition)
|
||||
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
|
||||
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
|
||||
let _ = self
|
||||
let _ = completed
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user