Merge commit 'de6f737bc106c29f9c0478d9456dd0d0bcee2d33'

This commit is contained in:
Isaac 2024-11-29 11:32:05 +04:00
commit acf5f3cd9f
25 changed files with 367 additions and 181 deletions

View File

@ -1244,7 +1244,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines) let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji { if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: [text.basicEmoji.0]) signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0], inputLanguageCode: "")
|> map { (nil, $0.items) }]) |> map { (nil, $0.items) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
@ -1260,17 +1260,11 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
) )
} }
} }
signals = signal signals = signal
|> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 }
let emoticons = keywords.flatMap { $0.emoticons } return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode)
for emoji in emoticons { |> map { (nil, $0.items) }]
signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0])
|> take(1)
|> map { (emoji, $0.items) })
}
return signals
} }
} }

View File

@ -26,8 +26,11 @@
@property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout); @property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout);
@property (nonatomic, copy) void (^captionIsAboveUpdated)(bool captionIsAbove); @property (nonatomic, copy) void (^captionIsAboveUpdated)(bool captionIsAbove);
@property (nonatomic, readonly) bool editing;
- (void)createInputPanelIfNeeded; - (void)createInputPanelIfNeeded;
- (void)beginEditing; - (void)beginEditing;
- (void)finishEditing;
- (void)enableDismissal; - (void)enableDismissal;
- (void)onAnimateOut; - (void)onAnimateOut;

View File

@ -175,7 +175,15 @@
[strongSelf.window endEditing:true]; [strongSelf.window endEditing:true];
strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_donePressed(strongSelf->_currentItem);
if (strongSelf->_captionMixin.editing) {
[strongSelf->_captionMixin finishEditing];
TGDispatchAfter(0.1, dispatch_get_main_queue(), ^{
strongSelf->_donePressed(strongSelf->_currentItem);
});
} else {
strongSelf->_donePressed(strongSelf->_currentItem);
}
[strongSelf->_captionMixin onAnimateOut]; [strongSelf->_captionMixin onAnimateOut];
}; };
@ -1428,7 +1436,7 @@
|| [view isDescendantOfView:_landscapeToolbarView] || [view isDescendantOfView:_landscapeToolbarView]
|| [view isDescendantOfView:_selectedPhotosView] || [view isDescendantOfView:_selectedPhotosView]
|| [view isDescendantOfView:_captionMixin.inputPanelView] || [view isDescendantOfView:_captionMixin.inputPanelView]
|| [view isDescendantOfView:_captionMixin.dismissView] || ([view isDescendantOfView:_captionMixin.dismissView] && _captionMixin.dismissView.alpha > 0.0)
|| [view isKindOfClass:[TGMenuButtonView class]]) || [view isKindOfClass:[TGMenuButtonView class]])
{ {

View File

@ -100,9 +100,9 @@ typedef enum
_currentTimeLabel.text = @"0:00"; _currentTimeLabel.text = @"0:00";
_currentTimeLabel.textColor = [UIColor whiteColor]; _currentTimeLabel.textColor = [UIColor whiteColor];
_currentTimeLabel.layer.shadowOffset = CGSizeMake(0.0, 0.0); _currentTimeLabel.layer.shadowOffset = CGSizeMake(0.0, 0.0);
_currentTimeLabel.layer.shadowRadius = 2.0; _currentTimeLabel.layer.shadowRadius = 4.0;
_currentTimeLabel.layer.shadowColor = [UIColor blackColor].CGColor; _currentTimeLabel.layer.shadowColor = [UIColor blackColor].CGColor;
_currentTimeLabel.layer.shadowOpacity = 0.4; _currentTimeLabel.layer.shadowOpacity = 0.6;
_currentTimeLabel.layer.rasterizationScale = TGScreenScaling(); _currentTimeLabel.layer.rasterizationScale = TGScreenScaling();
_currentTimeLabel.layer.shouldRasterize = true; _currentTimeLabel.layer.shouldRasterize = true;
[self addSubview:_currentTimeLabel]; [self addSubview:_currentTimeLabel];
@ -115,9 +115,9 @@ typedef enum
_inverseTimeLabel.textAlignment = NSTextAlignmentRight; _inverseTimeLabel.textAlignment = NSTextAlignmentRight;
_inverseTimeLabel.textColor = [UIColor whiteColor]; _inverseTimeLabel.textColor = [UIColor whiteColor];
_inverseTimeLabel.layer.shadowOffset = CGSizeMake(0.0, 0.0); _inverseTimeLabel.layer.shadowOffset = CGSizeMake(0.0, 0.0);
_inverseTimeLabel.layer.shadowRadius = 2.0; _inverseTimeLabel.layer.shadowRadius = 4.0;
_inverseTimeLabel.layer.shadowColor = [UIColor blackColor].CGColor; _inverseTimeLabel.layer.shadowColor = [UIColor blackColor].CGColor;
_inverseTimeLabel.layer.shadowOpacity = 0.4; _inverseTimeLabel.layer.shadowOpacity = 0.6;
_inverseTimeLabel.layer.rasterizationScale = TGScreenScaling(); _inverseTimeLabel.layer.rasterizationScale = TGScreenScaling();
_inverseTimeLabel.layer.shouldRasterize = true; _inverseTimeLabel.layer.shouldRasterize = true;
[self addSubview:_inverseTimeLabel]; [self addSubview:_inverseTimeLabel];

View File

@ -61,7 +61,6 @@
_inputPanel.sendPressed = ^(NSAttributedString *string) { _inputPanel.sendPressed = ^(NSAttributedString *string) {
__strong TGPhotoCaptionInputMixin *strongSelf = weakSelf; __strong TGPhotoCaptionInputMixin *strongSelf = weakSelf;
[TGViewController enableAutorotation]; [TGViewController enableAutorotation];
strongSelf->_dismissView.hidden = true;
strongSelf->_editing = false; strongSelf->_editing = false;
@ -74,9 +73,7 @@
[TGViewController disableAutorotation]; [TGViewController disableAutorotation];
[strongSelf beginEditing]; [strongSelf beginEditing];
strongSelf->_dismissView.hidden = false;
if (strongSelf.panelFocused != nil) if (strongSelf.panelFocused != nil)
strongSelf.panelFocused(); strongSelf.panelFocused();
@ -129,12 +126,13 @@
_dismissView = [[UIView alloc] initWithFrame:parentView.bounds]; _dismissView = [[UIView alloc] initWithFrame:parentView.bounds];
_dismissView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _dismissView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dismissView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4];
_dismissTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDismissTap:)]; _dismissTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDismissTap:)];
_dismissTapRecognizer.enabled = false; _dismissTapRecognizer.enabled = false;
[_dismissView addGestureRecognizer:_dismissTapRecognizer]; [_dismissView addGestureRecognizer:_dismissTapRecognizer];
//[parentView insertSubview:_dismissView belowSubview:_backgroundView]; [parentView insertSubview:_dismissView belowSubview:_inputPanelView];
} }
- (void)setCaption:(NSAttributedString *)caption - (void)setCaption:(NSAttributedString *)caption
@ -168,6 +166,12 @@
[self createDismissViewIfNeeded]; [self createDismissViewIfNeeded];
[self createInputPanelIfNeeded]; [self createInputPanelIfNeeded];
_dismissView.alpha = 0.0;
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_dismissView.alpha = 1.0f;
} completion:^(BOOL finished) {
}];
} }
- (void)enableDismissal - (void)enableDismissal
@ -177,19 +181,21 @@
#pragma mark - #pragma mark -
- (void)finishEditing {
if ([self.inputPanel dismissInput]) {
_editing = false;
if (self.finishedWithCaption != nil)
self.finishedWithCaption([_inputPanel caption]);
}
}
- (void)handleDismissTap:(UITapGestureRecognizer *)gestureRecognizer - (void)handleDismissTap:(UITapGestureRecognizer *)gestureRecognizer
{ {
if (gestureRecognizer.state != UIGestureRecognizerStateRecognized) if (gestureRecognizer.state != UIGestureRecognizerStateRecognized)
return; return;
if ([self.inputPanel dismissInput]) { [self finishEditing];
_editing = false;
[_dismissView removeFromSuperview];
if (self.finishedWithCaption != nil)
self.finishedWithCaption([_inputPanel caption]);
}
} }
#pragma mark - Input Panel Delegate #pragma mark - Input Panel Delegate
@ -228,6 +234,19 @@
_keyboardHeight = keyboardHeight; _keyboardHeight = keyboardHeight;
CGFloat fadeAlpha = 1.0;
if (keyboardHeight < FLT_EPSILON) {
fadeAlpha = 0.0;
}
if (ABS(_dismissView.alpha - fadeAlpha) > FLT_EPSILON) {
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_dismissView.alpha = fadeAlpha;
} completion:^(BOOL finished) {
}];
}
if (!UIInterfaceOrientationIsPortrait([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]) && !TGIsPad()) if (!UIInterfaceOrientationIsPortrait([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]) && !TGIsPad())
return; return;

View File

@ -155,7 +155,7 @@ public func legacyStoryMediaEditor(context: AccountContext, item: TGMediaEditabl
}) })
} }
public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) { public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media) let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media)
|> deliverOnMainQueue).start(next: { (value, isImage) in |> deliverOnMainQueue).start(next: { (value, isImage) in
guard case let .data(data) = value, data.complete else { guard case let .data(data) = value, data.complete else {
@ -215,7 +215,8 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle:
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: selectableResult, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3 in let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: selectableResult, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3 in
nativeGenerator(_1, _2, _3, nil) nativeGenerator(_1, _2, _3, nil)
}) })
sendMessagesWithSignals(signals, false, 0) let isCaptionAbove = editingContext?.isCaptionAbove() ?? false
sendMessagesWithSignals(signals, false, 0, isCaptionAbove)
}, dismissed: { [weak legacyController] in }, dismissed: { [weak legacyController] in
legacyController?.dismiss() legacyController?.dismiss()
}) })

View File

@ -2028,7 +2028,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
)) ))
return .single(resultGroups) return .single(resultGroups)
} else { } else {
let remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) let remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode)
return combineLatest( return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),

View File

@ -1628,9 +1628,9 @@ private func monetizationEntries(
if canViewRevenue { if canViewRevenue {
entries.append(.adsTonBalanceTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_BalanceTitle : presentationData.strings.Monetization_TonBalanceTitle)) entries.append(.adsTonBalanceTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_BalanceTitle : presentationData.strings.Monetization_TonBalanceTitle))
entries.append(.adsTonBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, data.balances.withdrawEnabled)) entries.append(.adsTonBalance(presentationData.theme, data, (isCreator || isBot) && data.balances.availableBalance > 0, data.balances.withdrawEnabled))
if isCreator { if isCreator || isBot {
let withdrawalInfoText: String let withdrawalInfoText: String
if data.balances.availableBalance == 0 { if data.balances.availableBalance == 0 {
withdrawalInfoText = presentationData.strings.Monetization_Balance_ZeroInfo withdrawalInfoText = presentationData.strings.Monetization_Balance_ZeroInfo

View File

@ -81,13 +81,20 @@ func _internal_randomGreetingSticker(account: Account) -> Signal<FoundStickerIte
} }
} }
func _internal_searchStickers(account: Account, query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { func _internal_searchStickers(account: Account, query: String?, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
if scope.isEmpty { if scope.isEmpty {
return .single(([], true)) return .single(([], true))
} }
var query = query var emoticon = emoticon
if query == ["\u{2764}"] { if emoticon == ["\u{2764}"] {
query = ["\u{2764}\u{FE0F}"] emoticon = ["\u{2764}\u{FE0F}"]
}
let cacheKey: String
if let query, !query.isEmpty {
cacheKey = query
} else {
cacheKey = emoticon.sorted().joined()
} }
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in
@ -98,7 +105,7 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) { for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
if let item = entry.contents.get(SavedStickerItem.self) { if let item = entry.contents.get(SavedStickerItem.self) {
for representation in item.stringRepresentations { for representation in item.stringRepresentations {
for queryItem in query { for queryItem in emoticon {
if representation.hasPrefix(queryItem) { if representation.hasPrefix(queryItem) {
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations)) result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
break break
@ -125,7 +132,7 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
currentItems.insert(file.fileId) currentItems.insert(file.fileId)
for case let .Sticker(displayText, _, _) in file.attributes { for case let .Sticker(displayText, _, _) in file.attributes {
for queryItem in query { for queryItem in emoticon {
if displayText.hasPrefix(queryItem) { if displayText.hasPrefix(queryItem) {
matchingRecentItemsIds.insert(file.fileId) matchingRecentItemsIds.insert(file.fileId)
break break
@ -143,12 +150,9 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
} }
} }
var searchQueries: [ItemCollectionSearchQuery] = query.map { queryItem -> ItemCollectionSearchQuery in let searchQueries: [ItemCollectionSearchQuery] = emoticon.map { queryItem -> ItemCollectionSearchQuery in
return .exact(ValueBoxKey(queryItem)) return .exact(ValueBoxKey(queryItem))
} }
if query == ["\u{2764}"] {
searchQueries = [.any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])]
}
var installedItems: [FoundStickerItem] = [] var installedItems: [FoundStickerItem] = []
var installedAnimatedItems: [FoundStickerItem] = [] var installedAnimatedItems: [FoundStickerItem] = []
@ -187,7 +191,7 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
continue continue
} }
if matchingRecentItemsIds.contains(file.fileId) { if matchingRecentItemsIds.contains(file.fileId) {
result.append(FoundStickerItem(file: file, stringRepresentations: query)) result.append(FoundStickerItem(file: file, stringRepresentations: emoticon))
} }
} }
@ -196,7 +200,7 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
continue continue
} }
if matchingRecentItemsIds.contains(file.fileId) { if matchingRecentItemsIds.contains(file.fileId) {
result.append(FoundStickerItem(file: file, stringRepresentations: query)) result.append(FoundStickerItem(file: file, stringRepresentations: emoticon))
} }
} }
@ -205,8 +209,7 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
result.append(contentsOf: installedItems) result.append(contentsOf: installedItems)
} }
let combinedQuery = query.joined(separator: "") var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)))?.get(CachedStickerQueryResult.self)
var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)))?.get(CachedStickerQueryResult.self)
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
@ -292,14 +295,17 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
} }
} }
let remote = account.network.request(Api.functions.messages.getStickers(emoticon: query.joined(separator: ""), hash: cached?.hash ?? 0)) let remote: Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError>
|> `catch` { _ -> Signal<Api.messages.Stickers, NoError> in if let query, !query.isEmpty {
return .single(.stickersNotModified) let flags: Int32 = 0
} remote = account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: 0, limit: 128, hash: cached?.hash ?? 0))
|> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in |> `catch` { _ -> Signal<Api.messages.FoundStickers, NoError> in
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in return .single(.foundStickersNotModified(flags: 0, nextOffset: nil))
switch result { }
case let .stickers(hash, stickers): |> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in
switch result {
case let .foundStickers(_, _, hash, stickers):
var result: [FoundStickerItem] = [] var result: [FoundStickerItem] = []
let currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId }) let currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
@ -369,19 +375,113 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt
} }
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) { if hash != 0, let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query.joined(separator: ""))), entry: entry) transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)), entry: entry)
} }
return (result, true) return (result, true)
case .stickersNotModified: case .foundStickersNotModified:
break break
}
return (tempResult, true)
}
}
} else {
remote = account.network.request(Api.functions.messages.getStickers(emoticon: emoticon.joined(separator: ""), hash: cached?.hash ?? 0))
|> `catch` { _ -> Signal<Api.messages.Stickers, NoError> in
return .single(.stickersNotModified)
}
|> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in
switch result {
case let .stickers(hash, stickers):
var result: [FoundStickerItem] = []
let currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
var premiumItems: [FoundStickerItem] = []
var otherItems: [FoundStickerItem] = []
for item in localItems {
if item.file.isPremiumSticker {
premiumItems.append(item)
} else {
otherItems.append(item)
}
}
var foundItems: [FoundStickerItem] = []
var foundAnimatedItems: [FoundStickerItem] = []
var foundPremiumItems: [FoundStickerItem] = []
var files: [TelegramMediaFile] = []
for sticker in stickers {
if let file = telegramMediaFileFromApiDocument(sticker, altDocuments: []), let id = file.id {
files.append(file)
if !currentItemIds.contains(id) {
if file.isPremiumSticker {
foundPremiumItems.append(FoundStickerItem(file: file, stringRepresentations: []))
} else if file.isAnimatedSticker || file.isVideoSticker {
foundAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: []))
} else {
foundItems.append(FoundStickerItem(file: file, stringRepresentations: []))
}
}
}
}
let allPremiumItems = premiumItems + foundPremiumItems
let allOtherItems = otherItems + foundAnimatedItems + foundItems
if isPremium {
let batchCount = Int(searchStickersConfiguration.normalStickersPerPremiumCount)
if batchCount == 0 {
result.append(contentsOf: allPremiumItems)
result.append(contentsOf: allOtherItems)
} else {
if allPremiumItems.isEmpty {
result.append(contentsOf: allOtherItems)
} else {
var i = 0
for premiumItem in allPremiumItems {
if i < allOtherItems.count {
for j in i ..< min(i + batchCount, allOtherItems.count) {
result.append(allOtherItems[j])
}
i += batchCount
}
result.append(premiumItem)
}
if i < allOtherItems.count {
for j in i ..< allOtherItems.count {
result.append(allOtherItems[j])
}
}
}
}
} else {
result.append(contentsOf: allOtherItems)
result.append(contentsOf: allPremiumItems.prefix(max(0, Int(searchStickersConfiguration.premiumStickersCount))))
}
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)), entry: entry)
}
return (result, true)
case .stickersNotModified:
break
}
return (tempResult, true)
} }
return (tempResult, true)
} }
} }
return .single((tempResult, false)) return .single((tempResult, false))
|> then(remote) |> then(
remote
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
)
} }
} }
@ -770,16 +870,24 @@ func _internal_searchStickers(account: Account, category: EmojiSearchCategories.
} }
} }
func _internal_searchEmoji(account: Account, query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { func _internal_searchEmoji(account: Account, query: String?, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
if scope.isEmpty { if scope.isEmpty {
return .single(([], true)) return .single(([], true))
} }
var query = query var emoticon = emoticon
if query == ["\u{2764}"] { if emoticon == ["\u{2764}"] {
query = ["\u{2764}\u{FE0F}"] emoticon = ["\u{2764}\u{FE0F}"]
} }
let combinedQuery = query.sorted().joined(separator: "")
let querySet = Set(query) let cacheKey: String
if let query, !query.isEmpty {
cacheKey = query
} else {
cacheKey = emoticon.sorted().joined()
}
let querySet = Set(emoticon)
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in
let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false
@ -812,7 +920,7 @@ func _internal_searchEmoji(account: Account, query: [String], scope: SearchStick
result.append(contentsOf: installedItems) result.append(contentsOf: installedItems)
} }
var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)))?.get(CachedStickerQueryResult.self) var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)))?.get(CachedStickerQueryResult.self)
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
@ -839,54 +947,99 @@ func _internal_searchEmoji(account: Account, query: [String], scope: SearchStick
} }
} }
let remote = account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: query.joined(separator: ""), hash: cached?.hash ?? 0))
|> `catch` { _ -> Signal<Api.EmojiList, NoError> in let remote: Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError>
return .single(.emojiListNotModified) if let query, !query.isEmpty {
} let flags: Int32 = 1 << 0
|> mapToSignal { result -> Signal<(files: [TelegramMediaFile], hash: Int64)?, NoError> in remote = account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: 0, limit: 128, hash: cached?.hash ?? 0))
switch result { |> `catch` { _ -> Signal<Api.messages.FoundStickers, NoError> in
case .emojiListNotModified: return .single(.foundStickersNotModified(flags: 0, nextOffset: nil))
return .single(nil)
case let .emojiList(hash, documentIds):
return TelegramEngine(account: account).stickers.resolveInlineStickers(fileIds: documentIds)
|> map { fileMap -> (files: [TelegramMediaFile], hash: Int64)? in
var files: [TelegramMediaFile] = []
for documentId in documentIds {
if let file = fileMap[documentId] {
files.append(file)
}
}
return (files, hash)
}
} }
} |> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
|> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in switch result {
if let (fileItems, hash) = result { case let .foundStickers(_, _, hash, stickers):
var result: [FoundStickerItem] = localItems var result: [FoundStickerItem] = localItems
var currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId }) var currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
var files: [TelegramMediaFile] = [] var files: [TelegramMediaFile] = []
for file in fileItems { for sticker in stickers {
files.append(file) guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else {
if !currentItemIds.contains(file.fileId) { continue
currentItemIds.insert(file.fileId) }
result.append(FoundStickerItem(file: file, stringRepresentations: [])) files.append(file)
if !currentItemIds.contains(file.fileId) {
currentItemIds.insert(file.fileId)
result.append(FoundStickerItem(file: file, stringRepresentations: []))
}
} }
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)), entry: entry)
}
return (result, true)
case .foundStickersNotModified:
break
} }
return (intermediateResult, true)
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) }
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) { }
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)), entry: entry) } else {
} remote = account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: emoticon.joined(separator: ""), hash: cached?.hash ?? 0))
|> `catch` { _ -> Signal<Api.EmojiList, NoError> in
return (result, true) return .single(.emojiListNotModified)
}
|> mapToSignal { result -> Signal<(files: [TelegramMediaFile], hash: Int64)?, NoError> in
switch result {
case .emojiListNotModified:
return .single(nil)
case let .emojiList(hash, documentIds):
return TelegramEngine(account: account).stickers.resolveInlineStickers(fileIds: documentIds)
|> map { fileMap -> (files: [TelegramMediaFile], hash: Int64)? in
var files: [TelegramMediaFile] = []
for documentId in documentIds {
if let file = fileMap[documentId] {
files.append(file)
}
}
return (files, hash)
}
}
}
|> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in
if let (fileItems, hash) = result {
var result: [FoundStickerItem] = localItems
var currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
var files: [TelegramMediaFile] = []
for file in fileItems {
files.append(file)
if !currentItemIds.contains(file.fileId) {
currentItemIds.insert(file.fileId)
result.append(FoundStickerItem(file: file, stringRepresentations: []))
}
}
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)), entry: entry)
}
return (result, true)
}
return (intermediateResult, true)
} }
return (intermediateResult, true)
} }
} }
return .single((intermediateResult, false)) return .single((intermediateResult, false))
|> then(remote) |> then(
remote
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
)
} }
} }

View File

@ -30,8 +30,8 @@ public extension TelegramEngine {
return _internal_randomGreetingSticker(account: self.account) return _internal_randomGreetingSticker(account: self.account)
} }
public func searchStickers(query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { public func searchStickers(query: String?, emoticon: [String], inputLanguageCode: String = "", scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
return _internal_searchStickers(account: self.account, query: query, scope: scope) return _internal_searchStickers(account: self.account, query: query, emoticon: emoticon, inputLanguageCode: inputLanguageCode, scope: scope)
} }
public func searchStickers(category: EmojiSearchCategories.Group, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { public func searchStickers(category: EmojiSearchCategories.Group, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
@ -287,15 +287,15 @@ public extension TelegramEngine {
return _internal_resolveInlineStickersLocal(postbox: self.account.postbox, fileIds: fileIds) return _internal_resolveInlineStickersLocal(postbox: self.account.postbox, fileIds: fileIds)
} }
public func searchEmoji(emojiString: [String]) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> { public func searchEmoji(query: String?, emoticon: [String], inputLanguageCode: String = "") -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> {
return _internal_searchEmoji(account: self.account, query: emojiString) return _internal_searchEmoji(account: self.account, query: query, emoticon: emoticon, inputLanguageCode: inputLanguageCode)
|> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in |> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in
return (items.map(\.file), isFinalResult) return (items.map(\.file), isFinalResult)
} }
} }
public func searchEmoji(category: EmojiSearchCategories.Group) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> { public func searchEmoji(category: EmojiSearchCategories.Group) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> {
return _internal_searchEmoji(account: self.account, query: category.identifiers) return _internal_searchEmoji(account: self.account, query: nil, emoticon: category.identifiers, inputLanguageCode: "")
|> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in |> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in
return (items.map(\.file), isFinalResult) return (items.map(\.file), isFinalResult)
} }

View File

@ -338,7 +338,7 @@ final class AvatarEditorScreenComponent: Component {
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
return combineLatest( return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),
combineLatest(keywords.map { context.engine.stickers.searchStickers(query: $0.emoticons) combineLatest(keywords.map { context.engine.stickers.searchStickers(query: query, emoticon: $0.emoticons, inputLanguageCode: languageCode)
|> map { items -> [FoundStickerItem] in |> map { items -> [FoundStickerItem] in
return items.items return items.items
} }

View File

@ -1020,6 +1020,7 @@ private final class CameraScreenComponent: CombinedComponent {
hasAccess: hasAllRequiredAccess, hasAccess: hasAllRequiredAccess,
hideControls: component.cameraState.collageProgress > 1.0 - .ulpOfOne, hideControls: component.cameraState.collageProgress > 1.0 - .ulpOfOne,
collageProgress: component.cameraState.collageProgress, collageProgress: component.cameraState.collageProgress,
collageCount: component.cameraState.isCollageEnabled ? component.cameraState.collageGrid.count : nil,
tintColor: controlsTintColor, tintColor: controlsTintColor,
shutterState: shutterState, shutterState: shutterState,
lastGalleryAsset: state.lastGalleryAsset, lastGalleryAsset: state.lastGalleryAsset,
@ -1683,6 +1684,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
fileprivate var additionalPreviewView: CameraSimplePreviewView fileprivate var additionalPreviewView: CameraSimplePreviewView
fileprivate let previewBlurView: BlurView fileprivate let previewBlurView: BlurView
fileprivate let mainPreviewBlurView: BlurView
private var mainPreviewSnapshotView: UIView? private var mainPreviewSnapshotView: UIView?
private var additionalPreviewSnapshotView: UIView? private var additionalPreviewSnapshotView: UIView?
fileprivate let previewFrameLeftDimView: UIView fileprivate let previewFrameLeftDimView: UIView
@ -1795,6 +1797,9 @@ public class CameraScreenImpl: ViewController, CameraScreen {
self.previewBlurView = BlurView() self.previewBlurView = BlurView()
self.previewBlurView.isUserInteractionEnabled = false self.previewBlurView.isUserInteractionEnabled = false
self.mainPreviewBlurView = BlurView()
self.mainPreviewBlurView.isUserInteractionEnabled = false
var isDualCameraEnabled = Camera.isDualCameraSupported(forRoundVideo: false) var isDualCameraEnabled = Camera.isDualCameraSupported(forRoundVideo: false)
if isDualCameraEnabled { if isDualCameraEnabled {
if let isDualCameraEnabledValue = UserDefaults.standard.object(forKey: "TelegramStoryCameraIsDualEnabled") as? NSNumber { if let isDualCameraEnabledValue = UserDefaults.standard.object(forKey: "TelegramStoryCameraIsDualEnabled") as? NSNumber {
@ -2122,9 +2127,17 @@ public class CameraScreenImpl: ViewController, CameraScreen {
} }
} }
if case .position = modeChange { if case .position = modeChange {
UIView.transition(with: self.previewContainerView, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: { if self.cameraState.isCollageEnabled {
self.previewBlurView.effect = UIBlurEffect(style: .dark) self.mainPreviewBlurView.frame = self.mainPreviewContainerView.bounds
}) self.mainPreviewContainerView.addSubview(self.mainPreviewBlurView)
UIView.transition(with: self.mainPreviewContainerView, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: {
self.mainPreviewBlurView.effect = UIBlurEffect(style: .dark)
})
} else {
UIView.transition(with: self.previewContainerView, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: {
self.previewBlurView.effect = UIBlurEffect(style: .dark)
})
}
} else { } else {
self.previewContainerView.insertSubview(self.previewBlurView, belowSubview: self.additionalPreviewContainerView) self.previewContainerView.insertSubview(self.previewBlurView, belowSubview: self.additionalPreviewContainerView)
@ -2137,6 +2150,13 @@ public class CameraScreenImpl: ViewController, CameraScreen {
self.previewBlurView.effect = UIBlurEffect(style: .dark) self.previewBlurView.effect = UIBlurEffect(style: .dark)
} }
} else { } else {
if self.mainPreviewBlurView.effect != nil {
UIView.animate(withDuration: 0.4, animations: {
self.mainPreviewBlurView.effect = nil
}, completion: { _ in
self.mainPreviewBlurView.removeFromSuperview()
})
}
if self.previewBlurView.effect != nil { if self.previewBlurView.effect != nil {
UIView.animate(withDuration: 0.4, animations: { UIView.animate(withDuration: 0.4, animations: {
self.previewBlurView.effect = nil self.previewBlurView.effect = nil
@ -2971,7 +2991,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
} }
self.updatingCollageProgress = true self.updatingCollageProgress = true
self.controller?.updateCameraState({ state in self.controller?.updateCameraState({ state in
return state.updatedCollageProgress(collageState.progress) return state.updatedCollageProgress(collageState.innerProgress)
}, transition: .spring(duration: 0.3)) }, transition: .spring(duration: 0.3))
self.updatingCollageProgress = false self.updatingCollageProgress = false
}) })

View File

@ -36,6 +36,7 @@ private final class ShutterButtonContentComponent: Component {
let shutterState: ShutterButtonState let shutterState: ShutterButtonState
let blobState: ShutterBlobView.BlobState let blobState: ShutterBlobView.BlobState
let collageProgress: Float let collageProgress: Float
let collageCount: Int?
let highlightedAction: ActionSlot<Bool> let highlightedAction: ActionSlot<Bool>
let updateOffsetX: ActionSlot<(CGFloat, ComponentTransition)> let updateOffsetX: ActionSlot<(CGFloat, ComponentTransition)>
let updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)> let updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)>
@ -47,6 +48,7 @@ private final class ShutterButtonContentComponent: Component {
shutterState: ShutterButtonState, shutterState: ShutterButtonState,
blobState: ShutterBlobView.BlobState, blobState: ShutterBlobView.BlobState,
collageProgress: Float, collageProgress: Float,
collageCount: Int?,
highlightedAction: ActionSlot<Bool>, highlightedAction: ActionSlot<Bool>,
updateOffsetX: ActionSlot<(CGFloat, ComponentTransition)>, updateOffsetX: ActionSlot<(CGFloat, ComponentTransition)>,
updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)> updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)>
@ -57,6 +59,7 @@ private final class ShutterButtonContentComponent: Component {
self.shutterState = shutterState self.shutterState = shutterState
self.blobState = blobState self.blobState = blobState
self.collageProgress = collageProgress self.collageProgress = collageProgress
self.collageCount = collageCount
self.highlightedAction = highlightedAction self.highlightedAction = highlightedAction
self.updateOffsetX = updateOffsetX self.updateOffsetX = updateOffsetX
self.updateOffsetY = updateOffsetY self.updateOffsetY = updateOffsetY
@ -81,6 +84,9 @@ private final class ShutterButtonContentComponent: Component {
if lhs.collageProgress != rhs.collageProgress { if lhs.collageProgress != rhs.collageProgress {
return false return false
} }
if lhs.collageCount != rhs.collageCount {
return false
}
return true return true
} }
@ -314,6 +320,8 @@ private final class ShutterButtonContentComponent: Component {
self.innerLayer.bounds = CGRect(origin: .zero, size: innerSize) self.innerLayer.bounds = CGRect(origin: .zero, size: innerSize)
self.innerLayer.position = CGPoint(x: maximumShutterSize.width / 2.0, y: maximumShutterSize.height / 2.0) self.innerLayer.position = CGPoint(x: maximumShutterSize.width / 2.0, y: maximumShutterSize.height / 2.0)
let totalProgress = component.collageCount.flatMap { 1.0 / Double($0) } ?? 1.0
self.progressLayer.bounds = CGRect(origin: .zero, size: maximumShutterSize) self.progressLayer.bounds = CGRect(origin: .zero, size: maximumShutterSize)
self.progressLayer.position = CGPoint(x: maximumShutterSize.width / 2.0, y: maximumShutterSize.height / 2.0) self.progressLayer.position = CGPoint(x: maximumShutterSize.width / 2.0, y: maximumShutterSize.height / 2.0)
transition.setShapeLayerPath(layer: self.progressLayer, path: ringPath) transition.setShapeLayerPath(layer: self.progressLayer, path: ringPath)
@ -321,10 +329,14 @@ private final class ShutterButtonContentComponent: Component {
self.progressLayer.strokeColor = videoRedColor.cgColor self.progressLayer.strokeColor = videoRedColor.cgColor
self.progressLayer.lineWidth = ringWidth + UIScreenPixel self.progressLayer.lineWidth = ringWidth + UIScreenPixel
self.progressLayer.lineCap = .round self.progressLayer.lineCap = .round
self.progressLayer.transform = CATransform3DMakeRotation(-.pi / 2.0, 0.0, 0.0, 1.0) if totalProgress < 1.0 {
self.progressLayer.transform = CATransform3DMakeRotation(-.pi / 2.0 + CGFloat(component.collageProgress) * 2.0 * .pi, 0.0, 0.0, 1.0)
} else {
self.progressLayer.transform = CATransform3DMakeRotation(-.pi / 2.0, 0.0, 0.0, 1.0)
}
let previousValue = self.progressLayer.strokeEnd let previousValue = self.progressLayer.strokeEnd
self.progressLayer.strokeEnd = CGFloat(recordingProgress ?? 0.0) self.progressLayer.strokeEnd = CGFloat(recordingProgress ?? 0.0) * totalProgress
self.progressLayer.animateStrokeEnd(from: previousValue, to: self.progressLayer.strokeEnd, duration: 0.33) self.progressLayer.animateStrokeEnd(from: previousValue, to: self.progressLayer.strokeEnd, duration: 0.33)
return maximumShutterSize return maximumShutterSize
@ -554,6 +566,7 @@ final class CaptureControlsComponent: Component {
let hasAccess: Bool let hasAccess: Bool
let hideControls: Bool let hideControls: Bool
let collageProgress: Float let collageProgress: Float
let collageCount: Int?
let tintColor: UIColor let tintColor: UIColor
let shutterState: ShutterButtonState let shutterState: ShutterButtonState
let lastGalleryAsset: PHAsset? let lastGalleryAsset: PHAsset?
@ -576,6 +589,7 @@ final class CaptureControlsComponent: Component {
hasAccess: Bool, hasAccess: Bool,
hideControls: Bool, hideControls: Bool,
collageProgress: Float, collageProgress: Float,
collageCount: Int?,
tintColor: UIColor, tintColor: UIColor,
shutterState: ShutterButtonState, shutterState: ShutterButtonState,
lastGalleryAsset: PHAsset?, lastGalleryAsset: PHAsset?,
@ -597,6 +611,7 @@ final class CaptureControlsComponent: Component {
self.hasAccess = hasAccess self.hasAccess = hasAccess
self.hideControls = hideControls self.hideControls = hideControls
self.collageProgress = collageProgress self.collageProgress = collageProgress
self.collageCount = collageCount
self.tintColor = tintColor self.tintColor = tintColor
self.shutterState = shutterState self.shutterState = shutterState
self.lastGalleryAsset = lastGalleryAsset self.lastGalleryAsset = lastGalleryAsset
@ -632,6 +647,9 @@ final class CaptureControlsComponent: Component {
if lhs.collageProgress != rhs.collageProgress { if lhs.collageProgress != rhs.collageProgress {
return false return false
} }
if lhs.collageCount != rhs.collageCount {
return false
}
if lhs.tintColor != rhs.tintColor { if lhs.tintColor != rhs.tintColor {
return false return false
} }
@ -1152,6 +1170,7 @@ final class CaptureControlsComponent: Component {
shutterState: component.shutterState, shutterState: component.shutterState,
blobState: blobState, blobState: blobState,
collageProgress: component.collageProgress, collageProgress: component.collageProgress,
collageCount: component.collageCount,
highlightedAction: self.shutterHightlightedAction, highlightedAction: self.shutterHightlightedAction,
updateOffsetX: self.shutterUpdateOffsetX, updateOffsetX: self.shutterUpdateOffsetX,
updateOffsetY: self.shutterUpdateOffsetY updateOffsetY: self.shutterUpdateOffsetY

View File

@ -938,7 +938,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError>
let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError>
if hasPremium { if hasPremium {
remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode)
remotePacksSignal = context.engine.stickers.searchEmojiSets(query: query) remotePacksSignal = context.engine.stickers.searchEmojiSets(query: query)
|> mapToSignal { localResult in |> mapToSignal { localResult in
return .single((localResult, false)) return .single((localResult, false))

View File

@ -348,7 +348,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines) let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji { if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: [text.basicEmoji.0]) signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0])
|> map { (nil, $0.items) }]) |> map { (nil, $0.items) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
@ -364,17 +364,11 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
) )
} }
} }
signals = signal signals = signal
|> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 }
let emoticons = keywords.flatMap { $0.emoticons } return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode)
for emoji in emoticons { |> map { (nil, $0.items) }]
signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0])
// |> take(1)
|> map { (emoji, $0.items) })
}
return signals
} }
} }

View File

@ -111,7 +111,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha
case .installed: case .installed:
scope = [.installed] scope = [.installed]
} }
return context.engine.stickers.searchStickers(query: [query.basicEmoji.0], scope: scope) return context.engine.stickers.searchStickers(query: nil, emoticon: [query.basicEmoji.0], scope: scope)
|> map { items -> [FoundStickerItem] in |> map { items -> [FoundStickerItem] in
return items.items return items.items
} }

View File

@ -1759,17 +1759,19 @@ public final class MessageInputPanelComponent: Component {
} }
} }
var lightFieldColor = UIColor(white: 1.0, alpha: 0.09)
var fieldBackgroundIsDark = false var fieldBackgroundIsDark = false
if component.useGrayBackground { if component.useGrayBackground {
fieldBackgroundIsDark = false fieldBackgroundIsDark = false
} else if component.style == .media { } else if component.style == .media {
fieldBackgroundIsDark = true fieldBackgroundIsDark = false
lightFieldColor = UIColor(white: 0.2, alpha: 0.45)
} else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText { } else if self.textFieldExternalState.hasText && component.alwaysDarkWhenHasText {
fieldBackgroundIsDark = true fieldBackgroundIsDark = true
} else if isEditing || component.style == .editor { } else if isEditing || component.style == .editor {
fieldBackgroundIsDark = true fieldBackgroundIsDark = true
} }
self.fieldBackgroundView.updateColor(color: fieldBackgroundIsDark ? UIColor(white: 0.0, alpha: 0.5) : UIColor(white: 1.0, alpha: 0.09), transition: transition.containedViewLayoutTransition) self.fieldBackgroundView.updateColor(color: fieldBackgroundIsDark ? UIColor(white: 0.0, alpha: 0.5) : lightFieldColor, transition: transition.containedViewLayoutTransition)
if let placeholder = self.placeholder.view, let vibrancyPlaceholderView = self.vibrancyPlaceholder.view { if let placeholder = self.placeholder.view, let vibrancyPlaceholderView = self.vibrancyPlaceholder.view {
placeholder.isHidden = self.textFieldExternalState.hasText placeholder.isHidden = self.textFieldExternalState.hasText
vibrancyPlaceholderView.isHidden = placeholder.isHidden vibrancyPlaceholderView.isHidden = placeholder.isHidden

View File

@ -687,7 +687,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
)) ))
return .single(resultGroups) return .single(resultGroups)
} else { } else {
let remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) let remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode)
return combineLatest( return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),

View File

@ -798,7 +798,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
let botsKey = ValueBoxKey(length: 8) let botsKey = ValueBoxKey(length: 8)
botsKey.setInt64(0, value: 0) botsKey.setInt64(0, value: 0)
let iconLoaded = Atomic<[EnginePeer.Id: Bool]>(value: [:]) //let iconLoaded = Atomic<[EnginePeer.Id: Bool]>(value: [:])
let bots = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.attachMenuBots, id: botsKey)) let bots = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.attachMenuBots, id: botsKey))
|> mapToSignal { entry -> Signal<[AttachMenuBot], NoError> in |> mapToSignal { entry -> Signal<[AttachMenuBot], NoError> in
let bots: [AttachMenuBots.Bot] = entry?.get(AttachMenuBots.self)?.bots ?? [] let bots: [AttachMenuBots.Bot] = entry?.get(AttachMenuBots.self)?.bots ?? []
@ -811,32 +811,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
if let maybePeer = peersMap[bot.peerId], let peer = maybePeer { if let maybePeer = peersMap[bot.peerId], let peer = maybePeer {
let resultBot = AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags) let resultBot = AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags)
if bot.flags.contains(.showInSettings) { if bot.flags.contains(.showInSettings) {
if let peer = PeerReference(peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { result.append(.single(resultBot))
let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon)
let signal: Signal<AttachMenuBot?, NoError>
if let _ = iconLoaded.with({ $0 })[peer.id] {
signal = .single(resultBot)
} else {
signal = .single(nil)
|> then(
preloadedBotIcon(account: context.account, fileReference: fileReference)
|> filter { $0 }
|> map { _ -> AttachMenuBot? in
return resultBot
}
|> afterNext { _ in
let _ = iconLoaded.modify { current in
var updated = current
updated[peer.id] = true
return updated
}
}
)
}
result.append(signal)
} else {
result.append(.single(resultBot))
}
} }
} }
} }

View File

@ -923,7 +923,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
} }
let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone() let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone()
} else { } else {
iconSignal = .single(UIImage(bundleImageName: "Settings/Menu/Websites")!) iconSignal = .single(UIImage())
} }
let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none
items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: {
@ -5276,7 +5276,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
transitionCompletion() transitionCompletion()
}, getCaptionPanelView: { }, getCaptionPanelView: {
return nil return nil
}, sendMessagesWithSignals: { [weak self] signals, _, _ in }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!) strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!)
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in |> deliverOnMainQueue).startStrict(next: { [weak self] messages in

View File

@ -370,7 +370,7 @@ final class BusinessIntroSetupScreenComponent: Component {
var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([])
if query.isSingleEmoji { if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: [query.basicEmoji.0]) signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [query.basicEmoji.0])
|> map { (nil, $0.items) }]) |> map { (nil, $0.items) }])
} else if query.count > 1, !languageCode.isEmpty && languageCode != "emoji" { } else if query.count > 1, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
@ -385,18 +385,12 @@ final class BusinessIntroSetupScreenComponent: Component {
} }
) )
} }
} }
signals = signal signals = signal
|> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 }
let emoticons = keywords.flatMap { $0.emoticons } return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode)
for emoji in emoticons { |> map { (nil, $0.items) }]
signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0])
|> take(1)
|> map { (emoji, $0.items) })
}
return signals
} }
} }

View File

@ -1080,7 +1080,7 @@ public class StickerPickerScreen: ViewController {
} }
let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError>
if hasPremium { if hasPremium {
remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode)
} else { } else {
remoteSignal = .single(([], true)) remoteSignal = .single(([], true))
} }

View File

@ -1462,9 +1462,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
transitionCompletion() transitionCompletion()
}, getCaptionPanelView: { [weak self] in }, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView(isFile: false) return self?.getCaptionPanelView(isFile: false)
}, sendMessagesWithSignals: { [weak self] signals, _, _ in }, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in
if let strongSelf = self { if let strongSelf = self {
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false) var parameters: ChatSendMessageActionSheetController.SendParameters?
if isCaptionAbove {
parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true)
}
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false, parameters: parameters)
} }
}, present: { [weak self] c, a in }, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
@ -3801,7 +3805,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView(isFile: true) return self?.getCaptionPanelView(isFile: true)
}, sendMessagesWithSignals: { [weak self] signals, _, _ in }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in }) strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
strongSelf.editMessageMediaWithLegacySignals(signals!) strongSelf.editMessageMediaWithLegacySignals(signals!)

View File

@ -95,7 +95,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
case .installed: case .installed:
scope = [.installed] scope = [.installed]
} }
return context.engine.stickers.searchStickers(query: [query.basicEmoji.0], scope: scope) return context.engine.stickers.searchStickers(query: nil, emoticon: [query.basicEmoji.0], scope: scope)
|> map { items -> [FoundStickerItem] in |> map { items -> [FoundStickerItem] in
return items.items return items.items
} }

View File

@ -1,5 +1,5 @@
{ {
"app": "11.4", "app": "11.5",
"xcode": "16.0", "xcode": "16.0",
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
"macos": "15.0" "macos": "15.0"