Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-07-26 02:45:06 +03:00
commit a4a358cdaf
17 changed files with 523 additions and 108 deletions

View File

@ -7893,6 +7893,9 @@ Sorry for the inconvenience.";
"EmojiPackActionInfo.MultipleRemovedText_1" = "%@ emoji pack is no longer in your emoji."; "EmojiPackActionInfo.MultipleRemovedText_1" = "%@ emoji pack is no longer in your emoji.";
"EmojiPackActionInfo.MultipleRemovedText_any" = "%@ emoji packs are no longer in your emoji."; "EmojiPackActionInfo.MultipleRemovedText_any" = "%@ emoji packs are no longer in your emoji.";
"StickerPackActionInfo.MultipleRemovedText_1" = "%@ sticker pack is no longer in your stickers.";
"StickerPackActionInfo.MultipleRemovedText_any" = "%@ sticker packs are no longer in your stickers.";
"MaskPackActionInfo.RemovedTitle" = "Masks Removed"; "MaskPackActionInfo.RemovedTitle" = "Masks Removed";
"MaskPackActionInfo.ArchivedTitle" = "Masks Archived"; "MaskPackActionInfo.ArchivedTitle" = "Masks Archived";
"MaskPackActionInfo.RemovedText" = "%@ is no longer in your masks."; "MaskPackActionInfo.RemovedText" = "%@ is no longer in your masks.";
@ -7936,3 +7939,6 @@ Sorry for the inconvenience.";
"EmojiInput.PremiumEmojiToast.Text" = "Subscribe to Telegram Premium to unlock premium emoji."; "EmojiInput.PremiumEmojiToast.Text" = "Subscribe to Telegram Premium to unlock premium emoji.";
"EmojiInput.PremiumEmojiToast.Action" = "More"; "EmojiInput.PremiumEmojiToast.Action" = "More";
"StickerPacks.DeleteEmojiPacksConfirmation_1" = "Delete 1 Emoji Pack";
"StickerPacks.DeleteEmojiPacksConfirmation_any" = "Delete %@ Emoji Packs";

View File

@ -31,7 +31,8 @@ void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l)
// because only we are `self`. And if it's already `self` then we already have // because only we are `self`. And if it's already `self` then we already have
// the lock, because we reset it to NULL before we unlock. So (thread == self) is // the lock, because we reset it to NULL before we unlock. So (thread == self) is
// invariant. // invariant.
#if AS_USE_OS_LOCK
const pthread_t s = pthread_self(); const pthread_t s = pthread_self();
if (os_unfair_lock_trylock(&l->_lock)) { if (os_unfair_lock_trylock(&l->_lock)) {
// Owned by nobody. We now have the lock. Assign self. // Owned by nobody. We now have the lock. Assign self.
@ -43,14 +44,28 @@ void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l)
os_unfair_lock_lock(&l->_lock); os_unfair_lock_lock(&l->_lock);
rul_set_thread(l, s); rul_set_thread(l, s);
} }
#else
const pthread_t s = pthread_self();
if (OSSpinLockTry(&l->_lock)) {
// Owned by nobody. We now have the lock. Assign self.
rul_set_thread(l, s);
} else if (rul_get_thread(l) == s) {
// Owned by self (recursive lock). nop.
} else {
// Owned by other thread. Block and then set thread to self.
OSSpinLockLock(&l->_lock);
rul_set_thread(l, s);
}
#endif
l->_count++; l->_count++;
} }
BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l) BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l)
{ {
// Same as Lock above. See comments there. // Same as Lock above. See comments there.
#if AS_USE_OS_LOCK
const pthread_t s = pthread_self(); const pthread_t s = pthread_self();
if (os_unfair_lock_trylock(&l->_lock)) { if (os_unfair_lock_trylock(&l->_lock)) {
// Owned by nobody. We now have the lock. Assign self. // Owned by nobody. We now have the lock. Assign self.
@ -61,6 +76,18 @@ BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l)
// Owned by other thread. Fail. // Owned by other thread. Fail.
return NO; return NO;
} }
#else
const pthread_t s = pthread_self();
if (OSSpinLockTry(&l->_lock)) {
// Owned by nobody. We now have the lock. Assign self.
rul_set_thread(l, s);
} else if (rul_get_thread(l) == s) {
// Owned by self (recursive lock). nop.
} else {
// Owned by other thread. Fail.
return NO;
}
#endif
l->_count++; l->_count++;
return YES; return YES;
@ -78,6 +105,10 @@ void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l)
// try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly // try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly
// think that we still own the lock and proceed without blocking. // think that we still own the lock and proceed without blocking.
rul_set_thread(l, NULL); rul_set_thread(l, NULL);
#if AS_USE_OS_LOCK
os_unfair_lock_unlock(&l->_lock); os_unfair_lock_unlock(&l->_lock);
#else
OSSpinLockUnlock(&l->_lock);
#endif
} }
} }

View File

@ -10,6 +10,15 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h> #import <AsyncDisplayKit/ASBaseDefines.h>
#import <pthread/pthread.h> #import <pthread/pthread.h>
#if defined(__aarch64__)
#define AS_USE_OS_LOCK true
#else
#define AS_USE_OS_LOCK false
#endif
#if AS_USE_OS_LOCK
#import <os/lock.h> #import <os/lock.h>
// Note: We don't use ATOMIC_VAR_INIT here because C++ compilers don't like it, // Note: We don't use ATOMIC_VAR_INIT here because C++ compilers don't like it,
@ -46,3 +55,43 @@ AS_EXTERN OS_UNFAIR_LOCK_AVAILABILITY
void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l); void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l);
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
#else
#import <libkern/OSAtomic.h>
// Note: We don't use ATOMIC_VAR_INIT here because C++ compilers don't like it,
// and it literally does absolutely nothing.
#define AS_RECURSIVE_UNFAIR_LOCK_INIT ((ASRecursiveUnfairLock){ OS_SPINLOCK_INIT, NULL, 0})
NS_ASSUME_NONNULL_BEGIN
typedef struct {
OSSpinLock _lock;
_Atomic(pthread_t) _thread; // Write-protected by lock
int _count; // Protected by lock
} ASRecursiveUnfairLock;
/**
* Lock, blocking if needed.
*/
AS_EXTERN
void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l);
/**
* Try to lock without blocking. Returns whether we took the lock.
*/
AS_EXTERN
BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l);
/**
* Unlock. Calling this on a thread that does not own
* the lock will result in an assertion failure, and undefined
* behavior if foundation assertions are disabled.
*/
AS_EXTERN
void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l);
NS_ASSUME_NONNULL_END
#endif

View File

@ -9,7 +9,6 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <os/lock.h>
#import <pthread.h> #import <pthread.h>
#import <AsyncDisplayKit/ASAssert.h> #import <AsyncDisplayKit/ASAssert.h>
@ -148,7 +147,11 @@ namespace AS {
success = _recursive.try_lock(); success = _recursive.try_lock();
break; break;
case Unfair: case Unfair:
#if AS_USE_OS_LOCK
success = os_unfair_lock_trylock(&_unfair); success = os_unfair_lock_trylock(&_unfair);
#else
success = OSSpinLockTry(&_unfair);
#endif
break; break;
case RecursiveUnfair: case RecursiveUnfair:
success = ASRecursiveUnfairLockTryLock(&_runfair); success = ASRecursiveUnfairLockTryLock(&_runfair);
@ -169,7 +172,11 @@ namespace AS {
_recursive.lock(); _recursive.lock();
break; break;
case Unfair: case Unfair:
#if AS_USE_OS_LOCK
os_unfair_lock_lock(&_unfair); os_unfair_lock_lock(&_unfair);
#else
OSSpinLockLock(&_unfair);
#endif
break; break;
case RecursiveUnfair: case RecursiveUnfair:
ASRecursiveUnfairLockLock(&_runfair); ASRecursiveUnfairLockLock(&_runfair);
@ -188,7 +195,11 @@ namespace AS {
_recursive.unlock(); _recursive.unlock();
break; break;
case Unfair: case Unfair:
#if AS_USE_OS_LOCK
os_unfair_lock_unlock(&_unfair); os_unfair_lock_unlock(&_unfair);
#else
OSSpinLockUnlock(&_unfair);
#endif
break; break;
case RecursiveUnfair: case RecursiveUnfair:
ASRecursiveUnfairLockUnlock(&_runfair); ASRecursiveUnfairLockUnlock(&_runfair);
@ -226,7 +237,11 @@ namespace AS {
} else { } else {
if (gMutex_unfair) { if (gMutex_unfair) {
_type = Unfair; _type = Unfair;
#if AS_USE_OS_LOCK
_unfair = OS_UNFAIR_LOCK_INIT; _unfair = OS_UNFAIR_LOCK_INIT;
#else
_unfair = OS_SPINLOCK_INIT;
#endif
} else { } else {
_type = Plain; _type = Plain;
new (&_plain) std::mutex(); new (&_plain) std::mutex();
@ -261,7 +276,11 @@ namespace AS {
Type _type; Type _type;
union { union {
os_unfair_lock _unfair; #if AS_USE_OS_LOCK
os_unfair_lock _unfair;
#else
OSSpinLock _unfair;
#endif
ASRecursiveUnfairLock _runfair; ASRecursiveUnfairLock _runfair;
std::mutex _plain; std::mutex _plain;
std::recursive_mutex _recursive; std::recursive_mutex _recursive;

View File

@ -951,13 +951,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}))) })))
} }
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in if !message._asMessage().isCopyProtected() {
c.dismiss(completion: { [weak self] in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
if let strongSelf = self { c.dismiss(completion: { [weak self] in
strongSelf.forwardMessages(messageIds: Set([message.id])) if let strongSelf = self {
} strongSelf.forwardMessages(messageIds: Set([message.id]))
}) }
}))) })
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: { [weak self] in c.dismiss(completion: { [weak self] in
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false) self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false)

View File

@ -1005,7 +1005,16 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
toolbarItem = StickersToolbarItem(selectedCount: selectedCount, actions: [.init(title: presentationData.strings.StickerPacks_ActionDelete, isEnabled: selectedCount > 0, action: { toolbarItem = StickersToolbarItem(selectedCount: selectedCount, actions: [.init(title: presentationData.strings.StickerPacks_ActionDelete, isEnabled: selectedCount > 0, action: {
let actionSheet = ActionSheetController(presentationData: presentationData) let actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.StickerPacks_DeleteStickerPacksConfirmation(selectedCount), color: .destructive, action: { [weak actionSheet] in
let title: String
switch mode {
case .emoji:
title = presentationData.strings.StickerPacks_DeleteEmojiPacksConfirmation(selectedCount)
default:
title = presentationData.strings.StickerPacks_DeleteStickerPacksConfirmation(selectedCount)
}
items.append(ActionSheetButtonItem(title: title, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
if case .modal = mode { if case .modal = mode {

View File

@ -620,17 +620,28 @@ private final class StickerPackContainer: ASDisplayNode {
} }
if installedCount == self.currentStickerPacks.count { if installedCount == self.currentStickerPacks.count {
var removedPacks: Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> = .single([])
for (info, _, _) in self.currentStickerPacks { for (info, _, _) in self.currentStickerPacks {
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) removedPacks = removedPacks
|> deliverOnMainQueue).start(next: { _ in |> mapToSignal { current -> Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> in
// guard let (positionInList, _) = indexAndItems else { return self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
// return |> map { result -> [(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])] in
// } if let result = result {
// if dismissed { return current + [(info, result.0, result.1)]
// actionPerformed?(info, items, .remove(positionInList: positionInList)) } else {
// } return current
}) }
}
}
} }
let _ = (removedPacks
|> deliverOnMainQueue).start(next: { [weak self] results in
if !results.isEmpty {
self?.controller?.actionPerformed?(results.map { result -> (StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) in
return (result.0 as! StickerPackCollectionInfo, result.2.map { $0 as! StickerPackItem }, .remove(positionInList: result.1))
})
}
})
} else { } else {
var installedPacks: [(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)] = [] var installedPacks: [(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)] = []
for (info, items, isInstalled) in self.currentStickerPacks { for (info, items, isInstalled) in self.currentStickerPacks {

View File

@ -2123,6 +2123,13 @@ public final class EmojiPagerContentComponent: Component {
} }
} }
private enum VisualItemKey: Hashable {
case item(id: ItemLayer.Key)
case header(groupId: AnyHashable)
case groupExpandButton(groupId: AnyHashable)
case groupActionButton(groupId: AnyHashable)
}
private let shimmerHostView: PortalSourceView? private let shimmerHostView: PortalSourceView?
private let standaloneShimmerEffect: StandaloneShimmerEffect? private let standaloneShimmerEffect: StandaloneShimmerEffect?
@ -3012,7 +3019,7 @@ public final class EmojiPagerContentComponent: Component {
self.updateScrollingOffset(isReset: false, transition: transition) self.updateScrollingOffset(isReset: false, transition: transition)
} }
private func updateVisibleItems(transition: Transition, attemptSynchronousLoads: Bool, previousItemPositions: [ItemLayer.Key: CGPoint]?, updatedItemPositions: [ItemLayer.Key: CGPoint]?) { private func updateVisibleItems(transition: Transition, attemptSynchronousLoads: Bool, previousItemPositions: [VisualItemKey: CGPoint]?, previousAbsoluteItemPositions: [VisualItemKey: CGPoint]? = nil, updatedItemPositions: [VisualItemKey: CGPoint]?, hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)? = nil) {
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let keyboardChildEnvironment = self.keyboardChildEnvironment, let itemLayout = self.itemLayout else { guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let keyboardChildEnvironment = self.keyboardChildEnvironment, let itemLayout = self.itemLayout else {
return return
} }
@ -3190,10 +3197,12 @@ public final class EmojiPagerContentComponent: Component {
let groupPremiumButton: ComponentView<Empty> let groupPremiumButton: ComponentView<Empty>
var groupPremiumButtonTransition = transition var groupPremiumButtonTransition = transition
var animateButtonIn = false
if let current = self.visibleGroupPremiumButtons[itemGroup.groupId] { if let current = self.visibleGroupPremiumButtons[itemGroup.groupId] {
groupPremiumButton = current groupPremiumButton = current
} else { } else {
groupPremiumButtonTransition = .immediate groupPremiumButtonTransition = .immediate
animateButtonIn = !transition.animation.isImmediate
groupPremiumButton = ComponentView<Empty>() groupPremiumButton = ComponentView<Empty>()
self.visibleGroupPremiumButtons[itemGroup.groupId] = groupPremiumButton self.visibleGroupPremiumButtons[itemGroup.groupId] = groupPremiumButton
} }
@ -3257,13 +3266,19 @@ public final class EmojiPagerContentComponent: Component {
) )
let groupPremiumButtonFrame = CGRect(origin: CGPoint(x: itemLayout.itemInsets.left, y: itemGroupLayout.frame.maxY - groupPremiumButtonSize.height + 1.0), size: groupPremiumButtonSize) let groupPremiumButtonFrame = CGRect(origin: CGPoint(x: itemLayout.itemInsets.left, y: itemGroupLayout.frame.maxY - groupPremiumButtonSize.height + 1.0), size: groupPremiumButtonSize)
if let view = groupPremiumButton.view { if let view = groupPremiumButton.view {
var animateIn = false
if view.superview == nil { if view.superview == nil {
animateIn = true
self.scrollView.addSubview(view) self.scrollView.addSubview(view)
} }
if animateButtonIn, !transition.animation.isImmediate {
if let previousItemPosition = previousItemPositions?[.groupActionButton(groupId: itemGroup.groupId)], transitionHintInstalledGroupId != itemGroup.groupId, transitionHintExpandedGroupId != itemGroup.groupId {
groupPremiumButtonTransition = transition
view.center = previousItemPosition
}
}
groupPremiumButtonTransition.setFrame(view: view, frame: groupPremiumButtonFrame) groupPremiumButtonTransition.setFrame(view: view, frame: groupPremiumButtonFrame)
if animateIn, !transition.animation.isImmediate { if animateButtonIn, !transition.animation.isImmediate {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
transition.animateScale(view: view, from: 0.01, to: 1.0) transition.animateScale(view: view, from: 0.01, to: 1.0)
} }
@ -3275,12 +3290,14 @@ public final class EmojiPagerContentComponent: Component {
validGroupExpandActionButtons.insert(itemGroup.groupId) validGroupExpandActionButtons.insert(itemGroup.groupId)
let groupId = itemGroup.groupId let groupId = itemGroup.groupId
var animateButtonIn = false
var groupExpandActionButtonTransition = transition var groupExpandActionButtonTransition = transition
let groupExpandActionButton: GroupExpandActionButton let groupExpandActionButton: GroupExpandActionButton
if let current = self.visibleGroupExpandActionButtons[itemGroup.groupId] { if let current = self.visibleGroupExpandActionButtons[itemGroup.groupId] {
groupExpandActionButton = current groupExpandActionButton = current
} else { } else {
groupExpandActionButtonTransition = .immediate groupExpandActionButtonTransition = .immediate
animateButtonIn = !transition.animation.isImmediate
groupExpandActionButton = GroupExpandActionButton(pressed: { [weak self] in groupExpandActionButton = GroupExpandActionButton(pressed: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -3292,6 +3309,13 @@ public final class EmojiPagerContentComponent: Component {
self.mirrorContentScrollView.layer.addSublayer(groupExpandActionButton.tintContainerLayer) self.mirrorContentScrollView.layer.addSublayer(groupExpandActionButton.tintContainerLayer)
} }
if animateButtonIn, !transition.animation.isImmediate {
if let previousItemPosition = previousItemPositions?[.groupExpandButton(groupId: itemGroup.groupId)], transitionHintInstalledGroupId != itemGroup.groupId, transitionHintExpandedGroupId != itemGroup.groupId {
groupExpandActionButtonTransition = transition
groupExpandActionButton.center = previousItemPosition
}
}
let baseItemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: collapsedItemIndex) let baseItemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: collapsedItemIndex)
let buttonSize = groupExpandActionButton.update(theme: keyboardChildEnvironment.theme, title: collapsedItemText) let buttonSize = groupExpandActionButton.update(theme: keyboardChildEnvironment.theme, title: collapsedItemText)
let buttonFrame = CGRect(origin: CGPoint(x: baseItemFrame.minX + floor((baseItemFrame.width - buttonSize.width) / 2.0), y: baseItemFrame.minY + floor((baseItemFrame.height - buttonSize.height) / 2.0)), size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(x: baseItemFrame.minX + floor((baseItemFrame.width - buttonSize.width) / 2.0), y: baseItemFrame.minY + floor((baseItemFrame.height - buttonSize.height) / 2.0)), size: buttonSize)
@ -3404,11 +3428,11 @@ public final class EmojiPagerContentComponent: Component {
itemTransition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) itemTransition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
if animateItemIn, !transition.animation.isImmediate { if animateItemIn, !transition.animation.isImmediate {
if let previousItemPosition = previousItemPositions?[itemId], transitionHintInstalledGroupId != itemId.groupId, transitionHintExpandedGroupId != itemId.groupId { if let previousItemPosition = previousItemPositions?[.item(id: itemId)], transitionHintInstalledGroupId != itemId.groupId, transitionHintExpandedGroupId != itemId.groupId {
itemTransition = transition itemTransition = transition
itemLayer.position = previousItemPosition itemLayer.position = previousItemPosition
} else { } else {
if let contentAnimation = contentAnimation, case .groupExpanded(id: itemGroup.groupId) = contentAnimation.type { if transitionHintInstalledGroupId == itemId.groupId || transitionHintExpandedGroupId == itemId.groupId {
itemLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) itemLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
} else { } else {
@ -3454,7 +3478,18 @@ public final class EmojiPagerContentComponent: Component {
removedIds.append(id) removedIds.append(id)
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
if let position = updatedItemPositions?[id], transitionHintInstalledGroupId != id.groupId { if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id.groupId {
if let previousAbsolutePosition = previousAbsoluteItemPositions?[.item(id: id)] {
itemLayer.position = self.convert(previousAbsolutePosition, to: self.scrollView)
transition.setPosition(layer: itemLayer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0))
}
itemLayer.opacity = 0.0
itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16)
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer()
})
} else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId {
transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
@ -3485,13 +3520,28 @@ public final class EmojiPagerContentComponent: Component {
removedGroupHeaderIds.append(id) removedGroupHeaderIds.append(id)
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
groupHeaderLayer.alpha = 0.0 var isAnimatingDisappearance = false
groupHeaderLayer.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2) if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[VisualItemKey.header(groupId: id)] {
groupHeaderLayer.center = self.convert(previousAbsolutePosition, to: self.scrollView)
transition.setPosition(layer: groupHeaderLayer.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0))
isAnimatingDisappearance = true
}
let tintContentLayer = groupHeaderLayer.tintContentLayer let tintContentLayer = groupHeaderLayer.tintContentLayer
groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in
groupHeaderLayer?.removeFromSuperview() if !isAnimatingDisappearance, let position = updatedItemPositions?[.header(groupId: id)] {
tintContentLayer?.removeFromSuperlayer() transition.setPosition(layer: groupHeaderLayer.layer, position: position, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in
}) groupHeaderLayer?.removeFromSuperview()
tintContentLayer?.removeFromSuperlayer()
})
} else {
groupHeaderLayer.alpha = 0.0
groupHeaderLayer.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16)
groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in
groupHeaderLayer?.removeFromSuperview()
tintContentLayer?.removeFromSuperlayer()
})
}
} else { } else {
groupHeaderLayer.removeFromSuperview() groupHeaderLayer.removeFromSuperview()
groupHeaderLayer.tintContentLayer.removeFromSuperlayer() groupHeaderLayer.tintContentLayer.removeFromSuperlayer()
@ -3516,9 +3566,35 @@ public final class EmojiPagerContentComponent: Component {
var removedGroupPremiumButtonIds: [AnyHashable] = [] var removedGroupPremiumButtonIds: [AnyHashable] = []
for (id, groupPremiumButton) in self.visibleGroupPremiumButtons { for (id, groupPremiumButton) in self.visibleGroupPremiumButtons {
if !validGroupPremiumButtonIds.contains(id) { if !validGroupPremiumButtonIds.contains(id), let buttonView = groupPremiumButton.view {
removedGroupPremiumButtonIds.append(id) if !transition.animation.isImmediate {
groupPremiumButton.view?.removeFromSuperview() var isAnimatingDisappearance = false
if let position = updatedItemPositions?[.groupActionButton(groupId: id)], position.y > buttonView.center.y {
} else if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[VisualItemKey.groupActionButton(groupId: id)] {
buttonView.center = self.convert(previousAbsolutePosition, to: self.scrollView)
transition.setPosition(layer: buttonView.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0))
isAnimatingDisappearance = true
}
if !isAnimatingDisappearance, let position = updatedItemPositions?[.groupActionButton(groupId: id)] {
buttonView.alpha = 0.0
buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak buttonView] _ in
buttonView?.removeFromSuperview()
})
transition.setPosition(layer: buttonView.layer, position: position)
} else {
buttonView.alpha = 0.0
if transitionHintExpandedGroupId == id || hintDisappearingGroupFrame?.groupId == id {
buttonView.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16)
}
buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak buttonView] _ in
buttonView?.removeFromSuperview()
})
}
} else {
removedGroupPremiumButtonIds.append(id)
buttonView.removeFromSuperview()
}
} }
} }
for id in removedGroupPremiumButtonIds { for id in removedGroupPremiumButtonIds {
@ -3530,14 +3606,32 @@ public final class EmojiPagerContentComponent: Component {
if !validGroupExpandActionButtons.contains(id) { if !validGroupExpandActionButtons.contains(id) {
removedGroupExpandActionButtonIds.append(id) removedGroupExpandActionButtonIds.append(id)
if !transition.animation.isImmediate && transitionHintExpandedGroupId == id { if !transition.animation.isImmediate {
button.alpha = 0.0 var isAnimatingDisappearance = false
button.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2) if self.visibleGroupHeaders[id] == nil, let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[.groupExpandButton(groupId: id)] {
button.center = self.convert(previousAbsolutePosition, to: self.scrollView)
button.tintContainerLayer.position = button.center
transition.setPosition(layer: button.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0))
isAnimatingDisappearance = true
}
let tintContainerLayer = button.tintContainerLayer let tintContainerLayer = button.tintContainerLayer
button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak button, weak tintContainerLayer] _ in
button?.removeFromSuperview() if !isAnimatingDisappearance, let position = updatedItemPositions?[.groupExpandButton(groupId: id)] {
tintContainerLayer?.removeFromSuperlayer() transition.setPosition(layer: button.layer, position: position, completion: { [weak button, weak tintContainerLayer] _ in
}) button?.removeFromSuperview()
tintContainerLayer?.removeFromSuperlayer()
})
} else {
button.alpha = 0.0
if transitionHintExpandedGroupId == id || hintDisappearingGroupFrame?.groupId == id {
button.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16)
}
button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak button, weak tintContainerLayer] _ in
button?.removeFromSuperview()
tintContainerLayer?.removeFromSuperlayer()
})
}
} else { } else {
button.removeFromSuperview() button.removeFromSuperview()
button.tintContainerLayer.removeFromSuperlayer() button.tintContainerLayer.removeFromSuperlayer()
@ -3627,15 +3721,34 @@ public final class EmojiPagerContentComponent: Component {
standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor) standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
} }
var previousItemPositions: [ItemLayer.Key: CGPoint]? var previousItemPositions: [VisualItemKey: CGPoint]?
var calculateUpdatedItemPositions = false var calculateUpdatedItemPositions = false
var updatedItemPositions: [ItemLayer.Key: CGPoint]? var updatedItemPositions: [VisualItemKey: CGPoint]?
var anchorItem: (key: ItemLayer.Key, frame: CGRect)? let contentAnimation = transition.userData(ContentAnimation.self)
var transitionHintInstalledGroupId: AnyHashable?
var transitionHintExpandedGroupId: AnyHashable?
if let contentAnimation = contentAnimation {
switch contentAnimation.type {
case let .groupInstalled(groupId):
transitionHintInstalledGroupId = groupId
case let .groupExpanded(groupId):
transitionHintExpandedGroupId = groupId
default:
break
}
}
let _ = transitionHintExpandedGroupId
var hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)?
var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:]
var anchorItems: [ItemLayer.Key: CGRect] = [:]
if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.itemGroups != component.itemGroups { if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.itemGroups != component.itemGroups {
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
var previousItemPositionsValue: [ItemLayer.Key: CGPoint] = [:] var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:]
for groupIndex in 0 ..< previousComponent.itemGroups.count { for groupIndex in 0 ..< previousComponent.itemGroups.count {
let itemGroup = previousComponent.itemGroups[groupIndex] let itemGroup = previousComponent.itemGroups[groupIndex]
for itemIndex in 0 ..< itemGroup.items.count { for itemIndex in 0 ..< itemGroup.items.count {
@ -3649,7 +3762,7 @@ public final class EmojiPagerContentComponent: Component {
continue continue
} }
let itemFrame = previousItemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex) let itemFrame = previousItemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex)
previousItemPositionsValue[itemKey] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) previousItemPositionsValue[.item(id: itemKey)] = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
} }
} }
previousItemPositions = previousItemPositionsValue previousItemPositions = previousItemPositionsValue
@ -3657,23 +3770,78 @@ public final class EmojiPagerContentComponent: Component {
} }
let effectiveVisibleBounds = CGRect(origin: self.scrollView.bounds.origin, size: self.effectiveVisibleSize) let effectiveVisibleBounds = CGRect(origin: self.scrollView.bounds.origin, size: self.effectiveVisibleSize)
let topVisibleDetectionBounds = effectiveVisibleBounds.offsetBy(dx: 0.0, dy: pagerEnvironment.containerInsets.top) let topVisibleDetectionBounds = effectiveVisibleBounds
for (key, itemLayer) in self.visibleItemLayers { for (key, itemLayer) in self.visibleItemLayers {
if !topVisibleDetectionBounds.intersects(itemLayer.frame) { if !topVisibleDetectionBounds.intersects(itemLayer.frame) {
continue continue
} }
if let anchorItemValue = anchorItem {
if itemLayer.frame.minY < anchorItemValue.frame.minY { let absoluteFrame = self.scrollView.convert(itemLayer.frame, to: self)
anchorItem = (key, itemLayer.frame)
} else if itemLayer.frame.minY == anchorItemValue.frame.minY && itemLayer.frame.minX < anchorItemValue.frame.minX { if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == key.groupId {
anchorItem = (key, itemLayer.frame) if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame {
hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame))
} else {
hintDisappearingGroupFrame = (key.groupId, absoluteFrame)
} }
previousAbsoluteItemPositions[.item(id: key)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY)
} else { } else {
anchorItem = (key, itemLayer.frame) anchorItems[key] = absoluteFrame
} }
} }
if let anchorItemValue = anchorItem {
anchorItem = (anchorItemValue.key, self.scrollView.convert(anchorItemValue.frame, to: self)) for (id, groupHeader) in self.visibleGroupHeaders {
if !topVisibleDetectionBounds.intersects(groupHeader.frame) {
continue
}
let absoluteFrame = self.scrollView.convert(groupHeader.frame, to: self)
if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id {
if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame {
hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame))
} else {
hintDisappearingGroupFrame = (id, absoluteFrame)
}
previousAbsoluteItemPositions[.header(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY)
}
}
for (id, button) in self.visibleGroupExpandActionButtons {
if !topVisibleDetectionBounds.intersects(button.frame) {
continue
}
let absoluteFrame = self.scrollView.convert(button.frame, to: self)
if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id {
if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame {
hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame))
} else {
hintDisappearingGroupFrame = (id, absoluteFrame)
}
previousAbsoluteItemPositions[.groupExpandButton(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY)
}
}
for (id, button) in self.visibleGroupPremiumButtons {
guard let buttonView = button.view else {
continue
}
if !topVisibleDetectionBounds.intersects(buttonView.frame) {
continue
}
let absoluteFrame = self.scrollView.convert(buttonView.frame, to: self)
if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id {
if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame {
hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame))
} else {
hintDisappearingGroupFrame = (id, absoluteFrame)
}
previousAbsoluteItemPositions[.groupActionButton(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY)
}
} }
} }
@ -3742,37 +3910,50 @@ public final class EmojiPagerContentComponent: Component {
} }
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating) self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating)
if let anchorItem = anchorItem { var animatedScrollOffset: CGFloat = 0.0
outer: for i in 0 ..< component.itemGroups.count { if !anchorItems.isEmpty {
if component.itemGroups[i].groupId != anchorItem.key.groupId { let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in
continue if lhs.value.minY != rhs.value.minY {
return lhs.value.minY < rhs.value.minY
} else {
return lhs.value.minX < rhs.value.minX
} }
for j in 0 ..< component.itemGroups[i].items.count { })
let itemKey: ItemLayer.Key
if let file = component.itemGroups[i].items[j].file { outer: for i in 0 ..< component.itemGroups.count {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: file.fileId, staticEmoji: nil) for anchorItem in sortedAnchorItems {
} else if let staticEmoji = component.itemGroups[i].items[j].staticEmoji { if component.itemGroups[i].groupId != anchorItem.0.groupId {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: nil, staticEmoji: staticEmoji)
} else {
continue continue
} }
for j in 0 ..< component.itemGroups[i].items.count {
if itemKey == anchorItem.key { let itemKey: ItemLayer.Key
let itemFrame = itemLayout.frame(groupIndex: i, itemIndex: j) if let file = component.itemGroups[i].items[j].file {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: file.fileId, staticEmoji: nil)
var contentOffsetY = itemFrame.minY - anchorItem.frame.minY } else if let staticEmoji = component.itemGroups[i].items[j].staticEmoji {
if contentOffsetY > self.scrollView.contentSize.height - self.scrollView.bounds.height { itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: nil, staticEmoji: staticEmoji)
contentOffsetY = self.scrollView.contentSize.height - self.scrollView.bounds.height } else {
} continue
if contentOffsetY < 0.0 {
contentOffsetY = 0.0
} }
let previousBounds = self.scrollView.bounds if itemKey == anchorItem.0 {
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) let itemFrame = itemLayout.frame(groupIndex: i, itemIndex: j)
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: previousBounds.minY - contentOffsetY), to: CGPoint(), additive: true)
var contentOffsetY = itemFrame.minY - anchorItem.1.minY
break outer if contentOffsetY > self.scrollView.contentSize.height - self.scrollView.bounds.height {
contentOffsetY = self.scrollView.contentSize.height - self.scrollView.bounds.height
}
if contentOffsetY < 0.0 {
contentOffsetY = 0.0
}
let previousBounds = self.scrollView.bounds
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false)
let scrollOffset = previousBounds.minY - contentOffsetY
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: scrollOffset), to: CGPoint(), additive: true)
animatedScrollOffset = scrollOffset
break outer
}
} }
} }
} }
@ -3781,9 +3962,10 @@ public final class EmojiPagerContentComponent: Component {
self.ignoreScrolling = false self.ignoreScrolling = false
if calculateUpdatedItemPositions { if calculateUpdatedItemPositions {
var updatedItemPositionsValue: [ItemLayer.Key: CGPoint] = [:] var updatedItemPositionsValue: [VisualItemKey: CGPoint] = [:]
for groupIndex in 0 ..< component.itemGroups.count { for groupIndex in 0 ..< component.itemGroups.count {
let itemGroup = component.itemGroups[groupIndex] let itemGroup = component.itemGroups[groupIndex]
let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex]
for itemIndex in 0 ..< itemGroup.items.count { for itemIndex in 0 ..< itemGroup.items.count {
let item = itemGroup.items[itemIndex] let item = itemGroup.items[itemIndex]
let itemKey: ItemLayer.Key let itemKey: ItemLayer.Key
@ -3795,13 +3977,24 @@ public final class EmojiPagerContentComponent: Component {
continue continue
} }
let itemFrame = itemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex) let itemFrame = itemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex)
updatedItemPositionsValue[itemKey] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) updatedItemPositionsValue[.item(id: itemKey)] = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
} }
let groupPremiumButtonFrame = CGRect(origin: CGPoint(x: itemLayout.itemInsets.left, y: itemGroupLayout.frame.maxY - itemLayout.premiumButtonHeight + 1.0), size: CGSize(width: itemLayout.width - itemLayout.itemInsets.left - itemLayout.itemInsets.right, height: itemLayout.premiumButtonHeight))
updatedItemPositionsValue[.groupActionButton(groupId: itemGroup.groupId)] = CGPoint(x: groupPremiumButtonFrame.midX, y: groupPremiumButtonFrame.midY)
} }
updatedItemPositions = updatedItemPositionsValue updatedItemPositions = updatedItemPositionsValue
} }
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating), previousItemPositions: previousItemPositions, updatedItemPositions: updatedItemPositions) if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame {
hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, self.scrollView.convert(hintDisappearingGroupFrameValue.frame, from: self))
}
for (id, position) in previousAbsoluteItemPositions {
previousAbsoluteItemPositions[id] = position.offsetBy(dx: 0.0, dy: animatedScrollOffset)
}
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating), previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
return availableSize return availableSize
} }

View File

@ -1833,7 +1833,7 @@ final class EntityKeyboardTopPanelComponent: Component {
} }
let isRound: Bool let isRound: Bool
if let string = activeContentItemId.base as? String, (string == "recent" || string == "static" || string == "trending") { if let string = activeContentItemId.base as? String, (string == "featuredTop" || string == "recent" || string == "static" || string == "trending") {
isRound = true isRound = true
} else { } else {
isRound = false isRound = false

View File

@ -553,11 +553,12 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
self.f() self.f()
} }
} }
let displayTimer = Foundation.Timer(timeInterval: CGFloat(self.frameSkip) / 60.0, target: TimerTarget { [weak self] in let frameInterval = Double(self.frameSkip) / 60.0
let displayTimer = Foundation.Timer(timeInterval: frameInterval, target: TimerTarget { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.animationTick() strongSelf.animationTick(frameInterval: frameInterval)
}, selector: #selector(TimerTarget.timerEvent), userInfo: nil, repeats: true) }, selector: #selector(TimerTarget.timerEvent), userInfo: nil, repeats: true)
self.displayTimer = displayTimer self.displayTimer = displayTimer
RunLoop.main.add(displayTimer, forMode: .common) RunLoop.main.add(displayTimer, forMode: .common)
@ -646,8 +647,8 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
self.isPlaying = isPlaying self.isPlaying = isPlaying
} }
private func animationTick() { private func animationTick(frameInterval: Double) {
let secondsPerFrame = Double(self.frameSkip) / 60.0 let secondsPerFrame = frameInterval
var tasks: [LoadFrameGroupTask] = [] var tasks: [LoadFrameGroupTask] = []
if let groupContext = self.groupContext { if let groupContext = self.groupContext {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1139,6 +1139,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
return true return true
})) }))
} else if actions.allSatisfy({
if case .remove = $0.2 {
return true
} else {
return false
}
}) {
let isEmoji = actions[0].0.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_MultipleRemovedText(Int32(actions.count)) : presentationData.strings.StickerPackActionInfo_MultipleRemovedText(Int32(actions.count)), undo: true, info: actions[0].0, topItem: actions[0].1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .undo = action {
var itemsAndIndices: [(StickerPackCollectionInfo, [StickerPackItem], Int)] = actions.compactMap { action -> (StickerPackCollectionInfo, [StickerPackItem], Int)? in
if case let .remove(index) = action.2 {
return (action.0, action.1, index)
} else {
return nil
}
}
itemsAndIndices.sort(by: { $0.2 < $1.2 })
for (info, items, index) in itemsAndIndices.reversed() {
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: index).start()
}
}
return true
}))
} }
} else if let (info, items, action) = actions.first { } else if let (info, items, action) = actions.first {
let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
@ -10963,7 +10988,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}) })
let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }, makeEntityInputView: { [weak self] in let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { [weak self] c in
self?.presentInGlobalOverlay(c)
}, makeEntityInputView: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return nil return nil
} }

View File

@ -793,8 +793,21 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.contentContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.contentContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
let visibleRootModalDismissProgress: CGFloat = 1.0 - self.inputPanelContainerNode.expansionFraction let isOverlay: Bool
if self.inputPanelContainerNode.expansionFraction != 0.0 { switch self.chatPresentationInterfaceState.mode {
case .overlay:
isOverlay = true
default:
isOverlay = false
}
let visibleRootModalDismissProgress: CGFloat
if isOverlay {
visibleRootModalDismissProgress = 1.0
} else {
visibleRootModalDismissProgress = 1.0 - self.inputPanelContainerNode.expansionFraction
}
if !isOverlay && self.inputPanelContainerNode.expansionFraction != 0.0 {
let navigationModalFrame: NavigationModalFrame let navigationModalFrame: NavigationModalFrame
var animateFromFraction: CGFloat? var animateFromFraction: CGFloat?
if let current = self.navigationModalFrame { if let current = self.navigationModalFrame {
@ -886,6 +899,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
containerNode.cornerRadius = 15.0 containerNode.cornerRadius = 15.0
containerNode.addSubnode(self.backgroundNode) containerNode.addSubnode(self.backgroundNode)
containerNode.addSubnode(self.historyNodeContainer) containerNode.addSubnode(self.historyNodeContainer)
self.contentContainerNode.isHidden = true
if let restrictedNode = self.restrictedNode { if let restrictedNode = self.restrictedNode {
containerNode.addSubnode(restrictedNode) containerNode.addSubnode(restrictedNode)
} }
@ -2663,6 +2677,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let inputMediaNode = self.inputMediaNode, self.inputNode === inputMediaNode {
let convertedPoint = self.view.convert(point, to: inputMediaNode.view)
if inputMediaNode.point(inside: convertedPoint, with: event) {
return inputMediaNode.hitTest(convertedPoint, with: event)
}
}
switch self.chatPresentationInterfaceState.mode { switch self.chatPresentationInterfaceState.mode {
case .standard(previewing: true): case .standard(previewing: true):
if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node, node is ChatMessageSelectionNode || node is GridMessageSelectionNode { if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node, node is ChatMessageSelectionNode || node is GridMessageSelectionNode {

View File

@ -2030,7 +2030,25 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
}, },
addGroupAction: { _, _ in addGroupAction: { _, _ in
}, },
clearGroup: { _ in clearGroup: { [weak self] groupId in
guard let strongSelf = self else {
return
}
if groupId == AnyHashable("recent") {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = context.engine.stickers.clearRecentlyUsedEmoji().start()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
strongSelf.presentController?(actionSheet)
}
}, },
pushController: { _ in pushController: { _ in
}, },
@ -2062,7 +2080,14 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
gifs: nil, gifs: nil,
availableGifSearchEmojies: [] availableGifSearchEmojies: []
), ),
updatedInputData: .never(), updatedInputData: ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isSecret: isSecret) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
return ChatEntityKeyboardInputNode.InputData(
emoji: emojiComponent,
stickers: nil,
gifs: nil,
availableGifSearchEmojies: []
)
},
defaultToEmojiTab: true, defaultToEmojiTab: true,
controllerInteraction: nil, controllerInteraction: nil,
interfaceInteraction: nil, interfaceInteraction: nil,

View File

@ -2659,6 +2659,15 @@ final class ChatMediaInputNode: ChatInputNode {
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition) self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: collectionListPanelOffset, transition: transition)
} }
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if self.panelIsFocused {
if point.y > -41.0 {
return true
}
}
return super.point(inside: point, with: event)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.panelIsFocused { if self.panelIsFocused {
if point.y > -41.0 && point.y < 38.0 { if point.y > -41.0 && point.y < 38.0 {

View File

@ -52,6 +52,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
} }
params.completion(controller) params.completion(controller)
} }
controller.purposefulAction = params.purposefulAction controller.purposefulAction = params.purposefulAction
if params.activateInput { if params.activateInput {
controller.activateInput() controller.activateInput()

View File

@ -71,6 +71,22 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
case never case never
} }
var fillWithColorUntilLoaded: UIColor? {
didSet {
if self.fillWithColorUntilLoaded != oldValue {
if let fillWithColorUntilLoaded = self.fillWithColorUntilLoaded {
if self.currentContents == nil {
self.backgroundColor = fillWithColorUntilLoaded.cgColor
} else {
self.backgroundColor = nil
}
} else {
self.backgroundColor = nil
}
}
}
}
var patternContentImage: UIImage? { var patternContentImage: UIImage? {
didSet { didSet {
if self.patternContentImage !== oldValue { if self.patternContentImage !== oldValue {
@ -252,6 +268,8 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver
self.allowSettingContents = true self.allowSettingContents = true
self.contents = contents?.cgImage self.contents = contents?.cgImage
self.allowSettingContents = false self.allowSettingContents = false
self.backgroundColor = nil
} }
} }
@ -836,16 +854,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.patternImageLayer.isHidden = false self.patternImageLayer.isHidden = false
let invertPattern = intensity < 0 let invertPattern = intensity < 0
self.patternImageLayer.fillWithColorUntilLoaded = invertPattern ? .black : nil
if invertPattern { if invertPattern {
self.backgroundColor = .black self.backgroundColor = .black
let contentAlpha = abs(intensity) let contentAlpha = abs(intensity)
self.gradientBackgroundNode?.contentView.alpha = contentAlpha self.gradientBackgroundNode?.contentView.alpha = contentAlpha
self.contentNode.alpha = contentAlpha self.contentNode.alpha = contentAlpha
if self.patternImageLayer.contents != nil {
self.patternImageLayer.backgroundColor = nil
} else {
self.patternImageLayer.backgroundColor = nil
}
} else { } else {
self.backgroundColor = nil self.backgroundColor = nil
self.gradientBackgroundNode?.contentView.alpha = 1.0 self.gradientBackgroundNode?.contentView.alpha = 1.0
@ -856,6 +872,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.patternImageDisposable.set(nil) self.patternImageDisposable.set(nil)
self.validPatternImage = nil self.validPatternImage = nil
self.patternImageLayer.isHidden = true self.patternImageLayer.isHidden = true
self.patternImageLayer.fillWithColorUntilLoaded = nil
self.patternImageLayer.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
self.backgroundColor = nil self.backgroundColor = nil
self.gradientBackgroundNode?.contentView.alpha = 1.0 self.gradientBackgroundNode?.contentView.alpha = 1.0
@ -953,11 +970,6 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if invertPattern { if invertPattern {
patternColor = .clear patternColor = .clear
patternBackgroundColor = .clear patternBackgroundColor = .clear
if self.patternImageLayer.contents == nil {
self.patternImageLayer.backgroundColor = UIColor.black.cgColor
} else {
self.patternImageLayer.backgroundColor = nil
}
} else { } else {
if patternIsLight { if patternIsLight {
patternColor = .black patternColor = .black