mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
@@ -350,7 +350,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var premiumButtonInset: CGFloat
|
||||
var premiumButtonHeight: CGFloat
|
||||
|
||||
init(width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], itemLayoutType: ItemLayoutType, expandedPremiumGroups: Set<AnyHashable>) {
|
||||
init(width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], itemLayoutType: ItemLayoutType) {
|
||||
self.width = width
|
||||
self.containerInsets = containerInsets
|
||||
|
||||
@@ -392,7 +392,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
let numRowsInGroup = (itemGroup.itemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
var groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * self.visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
|
||||
if itemGroup.isPremium && expandedPremiumGroups.contains(itemGroup.groupId) {
|
||||
if itemGroup.isPremium {
|
||||
groupContentSize.height += self.premiumButtonInset + self.premiumButtonHeight
|
||||
}
|
||||
self.itemGroupLayouts.append(ItemGroupLayout(
|
||||
@@ -547,12 +547,11 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
public private(set) var displayPlaceholder: Bool = false
|
||||
public let onUpdateDisplayPlaceholder: (Bool) -> Void
|
||||
|
||||
public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
|
||||
|
||||
public init(
|
||||
item: Item,
|
||||
context: AccountContext,
|
||||
groupId: String,
|
||||
attemptSynchronousLoad: Bool,
|
||||
file: TelegramMediaFile?,
|
||||
staticEmoji: String?,
|
||||
@@ -562,7 +561,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
blurredBadgeColor: UIColor,
|
||||
displayPremiumBadgeIfAvailable: Bool,
|
||||
pointSize: CGSize,
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool) -> Void
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
|
||||
) {
|
||||
self.item = item
|
||||
self.file = file
|
||||
@@ -583,7 +582,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.disposable = renderer.add(groupId: groupId, target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
||||
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||
|
||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||
@@ -614,13 +613,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
if attemptSynchronousLoad {
|
||||
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
|
||||
loadAnimation()
|
||||
} else {
|
||||
let _ = renderer.loadFirstFrame(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
||||
let _ = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
||||
loadAnimation()
|
||||
|
||||
if !success {
|
||||
@@ -692,7 +691,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.placeholderColor = layer.placeholderColor
|
||||
self.size = layer.size
|
||||
|
||||
self.onUpdateDisplayPlaceholder = { _ in }
|
||||
self.onUpdateDisplayPlaceholder = { _, _ in }
|
||||
|
||||
super.init(layer: layer)
|
||||
}
|
||||
@@ -728,47 +727,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
self.displayPlaceholder = displayPlaceholder
|
||||
self.onUpdateDisplayPlaceholder(displayPlaceholder)
|
||||
self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0)
|
||||
}
|
||||
|
||||
public override func transitionToContents(_ contents: AnyObject) {
|
||||
self.contents = contents
|
||||
|
||||
/*if displayPlaceholder {
|
||||
if self.placeholderView == nil {
|
||||
self.placeholderView = PortalView()
|
||||
if let placeholderView = self.placeholderView, let shimmerView = self.shimmerView {
|
||||
self.addSublayer(placeholderView.view.layer)
|
||||
placeholderView.view.frame = self.bounds
|
||||
shimmerView.addPortal(view: placeholderView)
|
||||
}
|
||||
}
|
||||
if self.placeholderMaskLayer == nil {
|
||||
self.placeholderMaskLayer = SimpleLayer()
|
||||
self.placeholderView?.view.layer.mask = self.placeholderMaskLayer
|
||||
}
|
||||
let file = self.file
|
||||
let size = self.size
|
||||
//let placeholderColor = self.placeholderColor
|
||||
|
||||
Queue.concurrentDefaultQueue().async { [weak self] in
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: .black) {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if strongSelf.displayPlaceholder {
|
||||
strongSelf.placeholderMaskLayer?.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = self.placeholderView {
|
||||
self.placeholderView = nil
|
||||
placeholderView.view.layer.removeFromSuperlayer()
|
||||
}
|
||||
if let _ = self.placeholderMaskLayer {
|
||||
self.placeholderMaskLayer = nil
|
||||
}
|
||||
}*/
|
||||
if self.displayPlaceholder {
|
||||
self.displayPlaceholder = false
|
||||
self.onUpdateDisplayPlaceholder(false, 0.2)
|
||||
self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -800,12 +769,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
private let boundsChangeTrackerLayer = SimpleLayer()
|
||||
private var effectiveVisibleSize: CGSize = CGSize()
|
||||
|
||||
private let placeholdersContainerView: UIView
|
||||
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
|
||||
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
|
||||
private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:]
|
||||
private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:]
|
||||
private var visibleGroupPremiumButtons: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private var expandedPremiumGroups: Set<AnyHashable> = Set()
|
||||
private var ignoreScrolling: Bool = false
|
||||
private var keepTopPanelVisibleUntilScrollingInput: Bool = false
|
||||
|
||||
@@ -822,12 +791,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shimmerHostView = PortalSourceView()
|
||||
|
||||
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||
|
||||
self.scrollView = ContentScrollView()
|
||||
self.scrollView.layer.anchorPoint = CGPoint()
|
||||
|
||||
self.placeholdersContainerView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.shimmerHostView.alpha = 0.0
|
||||
@@ -852,6 +822,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.placeholdersContainerView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
|
||||
@@ -1071,7 +1043,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let locationInScrollView = recognizer.location(in: self.scrollView)
|
||||
outer: for (id, groupHeader) in self.visibleGroupHeaders {
|
||||
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
|
||||
for group in component.itemGroups {
|
||||
let _ = id
|
||||
/*for group in component.itemGroups {
|
||||
if group.groupId == id {
|
||||
if group.isPremium && !self.expandedPremiumGroups.contains(id) {
|
||||
if self.expandedPremiumGroups.contains(id) {
|
||||
@@ -1096,7 +1069,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1325,7 +1298,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
groupBorderTransition.setFrame(layer: groupBorderLayer, frame: groupBorderFrame)
|
||||
|
||||
if self.expandedPremiumGroups.contains(itemGroup.groupId) {
|
||||
if itemGroup.isPremium {
|
||||
validGroupPremiumButtonIds.insert(itemGroup.groupId)
|
||||
|
||||
let groupPremiumButton: ComponentView<Empty>
|
||||
@@ -1414,7 +1387,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemLayer = ItemLayer(
|
||||
item: item,
|
||||
context: component.context,
|
||||
groupId: "keyboard-\(Int(itemLayout.nativeItemSize))",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
file: item.file,
|
||||
staticEmoji: item.staticEmoji,
|
||||
@@ -1424,7 +1396,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5),
|
||||
displayPremiumBadgeIfAvailable: itemGroup.displayPremiumBadges,
|
||||
pointSize: itemNativeFitSize,
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@@ -1442,7 +1414,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
size: itemNativeFitSize
|
||||
)
|
||||
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
strongSelf.scrollView.insertSubview(placeholderView, at: 0)
|
||||
strongSelf.placeholdersContainerView.addSubview(placeholderView)
|
||||
}
|
||||
placeholderView.frame = itemLayer.frame
|
||||
placeholderView.update(size: placeholderView.bounds.size)
|
||||
@@ -1452,9 +1424,20 @@ public final class EmojiPagerContentComponent: Component {
|
||||
} else {
|
||||
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
placeholderView.removeFromSuperview()
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
if duration > 0.0 {
|
||||
placeholderView.layer.opacity = 0.0
|
||||
placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak self, weak placeholderView] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
placeholderView?.removeFromSuperview()
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
})
|
||||
} else {
|
||||
placeholderView.removeFromSuperview()
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1481,7 +1464,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
} else if updateItemLayerPlaceholder {
|
||||
if itemLayer.displayPlaceholder {
|
||||
itemLayer.onUpdateDisplayPlaceholder(true)
|
||||
itemLayer.onUpdateDisplayPlaceholder(true, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1489,6 +1472,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
var removedPlaceholerViews = false
|
||||
var removedIds: [ItemLayer.Key] = []
|
||||
for (id, itemLayer) in self.visibleItemLayers {
|
||||
if !validIds.contains(id) {
|
||||
@@ -1501,6 +1485,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) {
|
||||
view.removeFromSuperview()
|
||||
removedPlaceholerViews = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,13 +1522,17 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.visibleGroupPremiumButtons.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
if removedPlaceholerViews {
|
||||
self.updateShimmerIfNeeded()
|
||||
}
|
||||
|
||||
if let topVisibleGroupId = topVisibleGroupId {
|
||||
self.activeItemUpdated?.invoke((topVisibleGroupId, .immediate))
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShimmerIfNeeded() {
|
||||
if self.visibleItemPlaceholderViews.isEmpty {
|
||||
if self.placeholdersContainerView.subviews.isEmpty {
|
||||
self.standaloneShimmerEffect.layer = nil
|
||||
} else {
|
||||
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer
|
||||
@@ -1583,7 +1572,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
var itemTransition = transition
|
||||
|
||||
let itemLayout = ItemLayout(width: availableSize.width, containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top + 9.0, left: pagerEnvironment.containerInsets.left + 12.0, bottom: 9.0 + pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right + 12.0), itemGroups: itemGroups, itemLayoutType: component.itemLayoutType, expandedPremiumGroups: expandedPremiumGroups)
|
||||
let itemLayout = ItemLayout(width: availableSize.width, containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top + 9.0, left: pagerEnvironment.containerInsets.left + 12.0, bottom: 9.0 + pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right + 12.0), itemGroups: itemGroups, itemLayoutType: component.itemLayoutType)
|
||||
if let previousItemLayout = self.itemLayout {
|
||||
if previousItemLayout.width != itemLayout.width {
|
||||
itemTransition = .immediate
|
||||
|
||||
@@ -76,8 +76,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let theme: PresentationTheme
|
||||
public let bottomInset: CGFloat
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
public let stickerContent: EmojiPagerContentComponent
|
||||
public let gifContent: GifPagerContentComponent
|
||||
public let stickerContent: EmojiPagerContentComponent?
|
||||
public let gifContent: GifPagerContentComponent?
|
||||
public let availableGifSearchEmojies: [GifSearchEmoji]
|
||||
public let defaultToEmojiTab: Bool
|
||||
public let externalTopPanelContainer: PagerExternalTopPanelContainer?
|
||||
@@ -85,7 +85,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let hideInputUpdated: (Bool, Bool, Transition) -> Void
|
||||
public let switchToTextInput: () -> Void
|
||||
public let switchToGifSubject: (GifPagerContentComponent.Subject) -> Void
|
||||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode
|
||||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode?
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let hiddenInputHeight: CGFloat
|
||||
public let isExpanded: Bool
|
||||
@@ -94,8 +94,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
theme: PresentationTheme,
|
||||
bottomInset: CGFloat,
|
||||
emojiContent: EmojiPagerContentComponent,
|
||||
stickerContent: EmojiPagerContentComponent,
|
||||
gifContent: GifPagerContentComponent,
|
||||
stickerContent: EmojiPagerContentComponent?,
|
||||
gifContent: GifPagerContentComponent?,
|
||||
availableGifSearchEmojies: [GifSearchEmoji],
|
||||
defaultToEmojiTab: Bool,
|
||||
externalTopPanelContainer: PagerExternalTopPanelContainer?,
|
||||
@@ -103,7 +103,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void,
|
||||
switchToTextInput: @escaping () -> Void,
|
||||
switchToGifSubject: @escaping (GifPagerContentComponent.Subject) -> Void,
|
||||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode,
|
||||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode?,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
hiddenInputHeight: CGFloat,
|
||||
isExpanded: Bool
|
||||
@@ -201,170 +201,179 @@ public final class EntityKeyboardComponent: Component {
|
||||
var contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(component.gifContent)))
|
||||
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
//TODO:localize
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "recent",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/RecentTabIcon",
|
||||
theme: component.theme,
|
||||
title: "Recent",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.recent)
|
||||
}
|
||||
))
|
||||
))
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "trending",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/TrendingGifs",
|
||||
theme: component.theme,
|
||||
title: "Trending",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.trending)
|
||||
}
|
||||
))
|
||||
))
|
||||
for emoji in component.availableGifSearchEmojies {
|
||||
let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
|
||||
if transition.userData(MarkInputCollapsed.self) != nil {
|
||||
self.searchComponent = nil
|
||||
}
|
||||
|
||||
if let gifContent = component.gifContent {
|
||||
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(gifContent)))
|
||||
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
//TODO:localize
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: emoji.emoji,
|
||||
id: "recent",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.stickerContent.context,
|
||||
file: emoji.file,
|
||||
animationCache: component.stickerContent.animationCache,
|
||||
animationRenderer: component.stickerContent.animationRenderer,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/RecentTabIcon",
|
||||
theme: component.theme,
|
||||
title: emoji.title,
|
||||
title: "Recent",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.emojiSearch(emoji.emoji))
|
||||
self?.component?.switchToGifSubject(.recent)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
let defaultActiveGifItemId: AnyHashable
|
||||
switch component.gifContent.subject {
|
||||
case .recent:
|
||||
defaultActiveGifItemId = "recent"
|
||||
case .trending:
|
||||
defaultActiveGifItemId = "trending"
|
||||
case let .emojiSearch(value):
|
||||
defaultActiveGifItemId = AnyHashable(value)
|
||||
}
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topGifItems,
|
||||
defaultActiveItemId: defaultActiveGifItemId,
|
||||
activeContentItemIdUpdated: gifsContentItemIdUpdated,
|
||||
reorderItems: { _ in
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "trending",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/TrendingGifs",
|
||||
theme: component.theme,
|
||||
title: "Trending",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.trending)
|
||||
}
|
||||
))
|
||||
))
|
||||
for emoji in component.availableGifSearchEmojies {
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: emoji.emoji,
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: emoji.file,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
animationRenderer: component.emojiContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
title: emoji.title,
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.emojiSearch(emoji.emoji))
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputGifsIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
let defaultActiveGifItemId: AnyHashable
|
||||
switch gifContent.subject {
|
||||
case .recent:
|
||||
defaultActiveGifItemId = "recent"
|
||||
case .trending:
|
||||
defaultActiveGifItemId = "trending"
|
||||
case let .emojiSearch(value):
|
||||
defaultActiveGifItemId = AnyHashable(value)
|
||||
}
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topGifItems,
|
||||
defaultActiveItemId: defaultActiveGifItemId,
|
||||
activeContentItemIdUpdated: gifsContentItemIdUpdated,
|
||||
reorderItems: { _ in
|
||||
}
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputGifsIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
|
||||
var topStickerItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
for itemGroup in component.stickerContent.itemGroups {
|
||||
if let id = itemGroup.supergroupId.base as? String {
|
||||
let iconMapping: [String: String] = [
|
||||
"saved": "Chat/Input/Media/SavedStickersTabIcon",
|
||||
"recent": "Chat/Input/Media/RecentTabIcon",
|
||||
"premium": "Chat/Input/Media/PremiumIcon"
|
||||
]
|
||||
let titleMapping: [String: String] = [
|
||||
"saved": "Saved",
|
||||
"recent": "Recent",
|
||||
"premium": "Premium"
|
||||
]
|
||||
if let iconName = iconMapping[id], let title = titleMapping[id] {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.supergroupId,
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: iconName,
|
||||
theme: component.theme,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
}
|
||||
))
|
||||
))
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
} else {
|
||||
if !itemGroup.items.isEmpty {
|
||||
if let file = itemGroup.items[0].file {
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
}
|
||||
|
||||
if let stickerContent = component.stickerContent {
|
||||
var topStickerItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
for itemGroup in stickerContent.itemGroups {
|
||||
if let id = itemGroup.supergroupId.base as? String {
|
||||
let iconMapping: [String: String] = [
|
||||
"saved": "Chat/Input/Media/SavedStickersTabIcon",
|
||||
"recent": "Chat/Input/Media/RecentTabIcon",
|
||||
"premium": "Chat/Input/Media/PremiumIcon"
|
||||
]
|
||||
let titleMapping: [String: String] = [
|
||||
"saved": "Saved",
|
||||
"recent": "Recent",
|
||||
"premium": "Premium"
|
||||
]
|
||||
if let iconName = iconMapping[id], let title = titleMapping[id] {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.supergroupId,
|
||||
isReorderable: true,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.stickerContent.context,
|
||||
file: file,
|
||||
animationCache: component.stickerContent.animationCache,
|
||||
animationRenderer: component.stickerContent.animationRenderer,
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: iconName,
|
||||
theme: component.theme,
|
||||
title: itemGroup.title ?? "",
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if !itemGroup.items.isEmpty {
|
||||
if let file = itemGroup.items[0].file {
|
||||
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.supergroupId,
|
||||
isReorderable: true,
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: stickerContent.context,
|
||||
file: file,
|
||||
animationCache: stickerContent.animationCache,
|
||||
animationRenderer: stickerContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
title: itemGroup.title ?? "",
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
contents.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(stickerContent)))
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topStickerItems,
|
||||
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.reorderPacks(category: .stickers, items: items)
|
||||
}
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputStickersIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: {
|
||||
stickerContent.inputInteraction.openStickerSettings()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
}
|
||||
let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(component.stickerContent)))
|
||||
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topStickerItems,
|
||||
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.reorderPacks(category: .stickers, items: items)
|
||||
}
|
||||
))))
|
||||
contentIcons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputStickersIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
))))
|
||||
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSearchIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: { [weak self] in
|
||||
self?.openSearch()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/EntityInputSettingsIcon",
|
||||
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
|
||||
maxSize: nil
|
||||
)),
|
||||
action: {
|
||||
component.stickerContent.inputInteraction.openStickerSettings()
|
||||
}
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
|
||||
let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(component.emojiContent)))
|
||||
@@ -477,7 +486,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
)),
|
||||
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
|
||||
theme: component.theme,
|
||||
overflowHeight: component.hiddenInputHeight
|
||||
overflowHeight: component.hiddenInputHeight,
|
||||
displayBackground: component.externalTopPanelContainer == nil
|
||||
)),
|
||||
externalTopPanelContainer: component.externalTopPanelContainer,
|
||||
bottomPanel: AnyComponent(EntityKeyboardBottomPanelComponent(
|
||||
@@ -521,10 +531,6 @@ public final class EntityKeyboardComponent: Component {
|
||||
)
|
||||
transition.setFrame(view: self.pagerView, frame: CGRect(origin: CGPoint(), size: pagerSize))
|
||||
|
||||
if transition.userData(MarkInputCollapsed.self) != nil {
|
||||
self.searchComponent = nil
|
||||
}
|
||||
|
||||
if let searchComponent = self.searchComponent {
|
||||
var animateIn = false
|
||||
let searchView: ComponentHostView<EntitySearchContentEnvironment>
|
||||
@@ -546,7 +552,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
component: AnyComponent(searchComponent),
|
||||
environment: {
|
||||
EntitySearchContentEnvironment(
|
||||
context: component.stickerContent.context,
|
||||
context: component.emojiContent.context,
|
||||
theme: component.theme,
|
||||
deviceMetrics: component.deviceMetrics
|
||||
)
|
||||
@@ -669,7 +675,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
case .emoji:
|
||||
namespace = Namespaces.ItemCollection.CloudEmojiPacks
|
||||
}
|
||||
let _ = (component.stickerContent.context.engine.stickers.reorderStickerPacks(namespace: namespace, itemIds: currentIds)
|
||||
let _ = (component.emojiContent.context.engine.stickers.reorderStickerPacks(namespace: namespace, itemIds: currentIds)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
||||
@@ -278,56 +278,64 @@ final class EntityKeyboardBottomPanelComponent: Component {
|
||||
|
||||
let navigateToContentId = panelEnvironment.navigateToContentId
|
||||
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
validIconIds.append(icon.id)
|
||||
|
||||
var iconTransition = transition
|
||||
let iconView: ComponentHostView<Empty>
|
||||
if let current = self.iconViews[icon.id] {
|
||||
iconView = current
|
||||
} else {
|
||||
iconTransition = .immediate
|
||||
iconView = ComponentHostView<Empty>()
|
||||
self.iconViews[icon.id] = iconView
|
||||
self.addSubview(iconView)
|
||||
if panelEnvironment.contentIcons.count > 1 {
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
validIconIds.append(icon.id)
|
||||
|
||||
var iconTransition = transition
|
||||
let iconView: ComponentHostView<Empty>
|
||||
if let current = self.iconViews[icon.id] {
|
||||
iconView = current
|
||||
} else {
|
||||
iconTransition = .immediate
|
||||
iconView = ComponentHostView<Empty>()
|
||||
self.iconViews[icon.id] = iconView
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
|
||||
let iconSize = iconView.update(
|
||||
transition: iconTransition,
|
||||
component: AnyComponent(BottomPanelIconComponent(
|
||||
content: icon.component,
|
||||
action: {
|
||||
navigateToContentId(icon.id)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
|
||||
iconInfos[icon.id] = (size: iconSize, transition: iconTransition)
|
||||
|
||||
if !iconTotalSize.width.isZero {
|
||||
iconTotalSize.width += iconSpacing
|
||||
}
|
||||
iconTotalSize.width += iconSize.width
|
||||
iconTotalSize.height = max(iconTotalSize.height, iconSize.height)
|
||||
}
|
||||
|
||||
let iconSize = iconView.update(
|
||||
transition: iconTransition,
|
||||
component: AnyComponent(BottomPanelIconComponent(
|
||||
content: icon.component,
|
||||
action: {
|
||||
navigateToContentId(icon.id)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
|
||||
iconInfos[icon.id] = (size: iconSize, transition: iconTransition)
|
||||
|
||||
if !iconTotalSize.width.isZero {
|
||||
iconTotalSize.width += iconSpacing
|
||||
}
|
||||
iconTotalSize.width += iconSize.width
|
||||
iconTotalSize.height = max(iconTotalSize.height, iconSize.height)
|
||||
}
|
||||
|
||||
var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0) + 2.0)
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else {
|
||||
continue
|
||||
var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0))
|
||||
if component.bottomInset > 0.0 {
|
||||
nextIconOrigin.y += 2.0
|
||||
}
|
||||
|
||||
if panelEnvironment.contentIcons.count > 1 {
|
||||
for icon in panelEnvironment.contentIcons {
|
||||
guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else {
|
||||
continue
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size)
|
||||
iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil)
|
||||
|
||||
if let activeContentId = activeContentId, activeContentId == icon.id {
|
||||
self.highlightedIconBackgroundView.isHidden = false
|
||||
transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
|
||||
}
|
||||
|
||||
nextIconOrigin.x += iconInfo.size.width + iconSpacing
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size)
|
||||
iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil)
|
||||
|
||||
if let activeContentId = activeContentId, activeContentId == icon.id {
|
||||
self.highlightedIconBackgroundView.isHidden = false
|
||||
transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
|
||||
}
|
||||
|
||||
nextIconOrigin.x += iconInfo.size.width + iconSpacing
|
||||
}
|
||||
|
||||
if activeContentId == nil {
|
||||
|
||||
@@ -32,13 +32,16 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
|
||||
let theme: PresentationTheme
|
||||
let overflowHeight: CGFloat
|
||||
let displayBackground: Bool
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
overflowHeight: CGFloat
|
||||
overflowHeight: CGFloat,
|
||||
displayBackground: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.overflowHeight = overflowHeight
|
||||
self.displayBackground = displayBackground
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopContainerPanelComponent, rhs: EntityKeyboardTopContainerPanelComponent) -> Bool {
|
||||
@@ -48,6 +51,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
if lhs.overflowHeight != rhs.overflowHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.displayBackground != rhs.displayBackground {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -59,6 +65,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var backgroundView: BlurredBackgroundView?
|
||||
private var backgroundSeparatorView: UIView?
|
||||
|
||||
private var panelViews: [AnyHashable: PanelView] = [:]
|
||||
|
||||
private var component: EntityKeyboardTopContainerPanelComponent?
|
||||
@@ -183,6 +192,40 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
strongSelf.updateVisibilityFraction(value: fraction, transition: transition)
|
||||
}
|
||||
|
||||
if component.displayBackground {
|
||||
let backgroundView: BlurredBackgroundView
|
||||
if let current = self.backgroundView {
|
||||
backgroundView = current
|
||||
} else {
|
||||
backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
self.insertSubview(backgroundView, at: 0)
|
||||
}
|
||||
|
||||
let backgroundSeparatorView: UIView
|
||||
if let current = self.backgroundSeparatorView {
|
||||
backgroundSeparatorView = current
|
||||
} else {
|
||||
backgroundSeparatorView = UIView()
|
||||
self.insertSubview(backgroundSeparatorView, aboveSubview: backgroundView)
|
||||
}
|
||||
|
||||
backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate)
|
||||
backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)))
|
||||
|
||||
backgroundSeparatorView.backgroundColor = component.theme.chat.inputPanel.panelSeparatorColor
|
||||
transition.setFrame(view: backgroundSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
} else {
|
||||
if let backgroundView = self.backgroundView {
|
||||
self.backgroundView = nil
|
||||
backgroundView.removeFromSuperview()
|
||||
}
|
||||
if let backgroundSeparatorView = self.backgroundSeparatorView {
|
||||
self.backgroundSeparatorView = nil
|
||||
backgroundSeparatorView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
subgroupId: nil
|
||||
),
|
||||
context: component.context,
|
||||
groupId: "topPanel",
|
||||
attemptSynchronousLoad: false,
|
||||
file: component.file,
|
||||
staticEmoji: nil,
|
||||
@@ -110,18 +109,18 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
blurredBadgeColor: .clear,
|
||||
displayPremiumBadgeIfAvailable: false,
|
||||
pointSize: CGSize(width: 44.0, height: 44.0),
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: displayPlaceholder)
|
||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: displayPlaceholder, duration: duration)
|
||||
}
|
||||
)
|
||||
self.itemLayer = itemLayer
|
||||
self.layer.addSublayer(itemLayer)
|
||||
|
||||
if itemLayer.displayPlaceholder {
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +169,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
return availableSize
|
||||
}
|
||||
|
||||
private func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
private func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) {
|
||||
if displayPlaceholder {
|
||||
if self.placeholderView == nil, let component = self.component {
|
||||
let placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
|
||||
@@ -188,7 +187,15 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
} else {
|
||||
if let placeholderView = self.placeholderView {
|
||||
self.placeholderView = nil
|
||||
placeholderView.removeFromSuperview()
|
||||
|
||||
if duration > 0.0 {
|
||||
placeholderView.alpha = 0.0
|
||||
placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak placeholderView] _ in
|
||||
placeholderView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
placeholderView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +51,11 @@ final class EntitySearchContentEnvironment: Equatable {
|
||||
final class EntitySearchContentComponent: Component {
|
||||
typealias EnvironmentType = EntitySearchContentEnvironment
|
||||
|
||||
let makeContainerNode: () -> EntitySearchContainerNode
|
||||
let makeContainerNode: () -> EntitySearchContainerNode?
|
||||
let dismissSearch: () -> Void
|
||||
|
||||
init(
|
||||
makeContainerNode: @escaping () -> EntitySearchContainerNode,
|
||||
makeContainerNode: @escaping () -> EntitySearchContainerNode?,
|
||||
dismissSearch: @escaping () -> Void
|
||||
) {
|
||||
self.makeContainerNode = makeContainerNode
|
||||
@@ -78,30 +78,34 @@ final class EntitySearchContentComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: EntitySearchContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let containerNode: EntitySearchContainerNode
|
||||
let containerNode: EntitySearchContainerNode?
|
||||
if let current = self.containerNode {
|
||||
containerNode = current
|
||||
} else {
|
||||
containerNode = component.makeContainerNode()
|
||||
self.containerNode = containerNode
|
||||
self.addSubnode(containerNode)
|
||||
if let containerNode = containerNode {
|
||||
self.containerNode = containerNode
|
||||
self.addSubnode(containerNode)
|
||||
}
|
||||
}
|
||||
|
||||
if let containerNode = containerNode {
|
||||
|
||||
let environmentValue = environment[EntitySearchContentEnvironment.self].value
|
||||
|
||||
transition.setFrame(view: containerNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
containerNode.updateLayout(
|
||||
size: availableSize,
|
||||
leftInset: 0.0,
|
||||
rightInset: 0.0,
|
||||
bottomInset: 0.0,
|
||||
inputHeight: 0.0,
|
||||
deviceMetrics: environmentValue.deviceMetrics,
|
||||
transition: transition.containedViewLayoutTransition
|
||||
)
|
||||
|
||||
containerNode.onCancel = {
|
||||
component.dismissSearch()
|
||||
transition.setFrame(view: containerNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
containerNode.updateLayout(
|
||||
size: availableSize,
|
||||
leftInset: 0.0,
|
||||
rightInset: 0.0,
|
||||
bottomInset: 0.0,
|
||||
inputHeight: 0.0,
|
||||
deviceMetrics: environmentValue.deviceMetrics,
|
||||
transition: transition.containedViewLayoutTransition
|
||||
)
|
||||
|
||||
containerNode.onCancel = {
|
||||
component.dismissSearch()
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
|
||||
@@ -19,10 +19,11 @@ import SoftwareVideo
|
||||
import AVFoundation
|
||||
import PhotoResources
|
||||
import ContextUI
|
||||
import ShimmerEffect
|
||||
|
||||
private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
||||
private let context: AccountContext
|
||||
private let file: TelegramMediaFile
|
||||
private let file: TelegramMediaFile?
|
||||
|
||||
private var frameManager: SoftwareVideoLayerFrameManager?
|
||||
|
||||
@@ -56,7 +57,7 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
||||
}
|
||||
}
|
||||
|
||||
init(context: AccountContext, file: TelegramMediaFile, synchronousLoad: Bool) {
|
||||
init(context: AccountContext, file: TelegramMediaFile?, synchronousLoad: Bool) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
|
||||
@@ -64,29 +65,31 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
||||
|
||||
self.videoGravity = .resizeAspectFill
|
||||
|
||||
if let dimensions = file.dimensions {
|
||||
self.thumbnailDisposable = (mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .savedGif(media: self.file), synchronousLoad: synchronousLoad, nilForEmptyResult: true)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let boundingSize = CGSize(width: 93.0, height: 93.0)
|
||||
let imageSize = dimensions.cgSize.aspectFilled(boundingSize)
|
||||
|
||||
if let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .fill(.clear)))?.generateImage() {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.contents = image.cgImage
|
||||
strongSelf.setupVideo()
|
||||
strongSelf.started?()
|
||||
}
|
||||
if let file = self.file {
|
||||
if let dimensions = file.dimensions {
|
||||
self.thumbnailDisposable = (mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .savedGif(media: file), synchronousLoad: synchronousLoad, nilForEmptyResult: true)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
strongSelf.setupVideo()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.setupVideo()
|
||||
let boundingSize = CGSize(width: 93.0, height: 93.0)
|
||||
let imageSize = dimensions.cgSize.aspectFilled(boundingSize)
|
||||
|
||||
if let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .fill(.clear)))?.generateImage() {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.contents = image.cgImage
|
||||
strongSelf.setupVideo()
|
||||
strongSelf.started?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strongSelf.setupVideo()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.setupVideo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +106,10 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
||||
}
|
||||
|
||||
private func setupVideo() {
|
||||
let frameManager = SoftwareVideoLayerFrameManager(account: self.context.account, fileReference: .savedGif(media: self.file), layerHolder: nil, layer: self)
|
||||
guard let file = self.file else {
|
||||
return
|
||||
}
|
||||
let frameManager = SoftwareVideoLayerFrameManager(account: self.context.account, fileReference: .savedGif(media: file), layerHolder: nil, layer: self)
|
||||
self.frameManager = frameManager
|
||||
frameManager.started = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@@ -127,13 +133,16 @@ public final class GifPagerContentComponent: Component {
|
||||
public final class InputInteraction {
|
||||
public let performItemAction: (Item, UIView, CGRect) -> Void
|
||||
public let openGifContextMenu: (TelegramMediaFile, UIView, CGRect, ContextGesture, Bool) -> Void
|
||||
public let loadMore: (String) -> Void
|
||||
|
||||
public init(
|
||||
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
|
||||
openGifContextMenu: @escaping (TelegramMediaFile, UIView, CGRect, ContextGesture, Bool) -> Void
|
||||
openGifContextMenu: @escaping (TelegramMediaFile, UIView, CGRect, ContextGesture, Bool) -> Void,
|
||||
loadMore: @escaping (String) -> Void
|
||||
) {
|
||||
self.performItemAction = performItemAction
|
||||
self.openGifContextMenu = openGifContextMenu
|
||||
self.loadMore = loadMore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,17 +169,23 @@ public final class GifPagerContentComponent: Component {
|
||||
public let inputInteraction: InputInteraction
|
||||
public let subject: Subject
|
||||
public let items: [Item]
|
||||
public let isLoading: Bool
|
||||
public let loadMoreToken: String?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
inputInteraction: InputInteraction,
|
||||
subject: Subject,
|
||||
items: [Item]
|
||||
items: [Item],
|
||||
isLoading: Bool,
|
||||
loadMoreToken: String?
|
||||
) {
|
||||
self.context = context
|
||||
self.inputInteraction = inputInteraction
|
||||
self.subject = subject
|
||||
self.items = items
|
||||
self.isLoading = isLoading
|
||||
self.loadMoreToken = loadMoreToken
|
||||
}
|
||||
|
||||
public static func ==(lhs: GifPagerContentComponent, rhs: GifPagerContentComponent) -> Bool {
|
||||
@@ -186,6 +201,12 @@ public final class GifPagerContentComponent: Component {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.isLoading != rhs.isLoading {
|
||||
return false
|
||||
}
|
||||
if lhs.loadMoreToken != rhs.loadMoreToken {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -256,7 +277,7 @@ public final class GifPagerContentComponent: Component {
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY - self.verticalSpacing) / (self.itemSize + self.verticalSpacing)))
|
||||
|
||||
let minVisibleIndex = minVisibleRow * self.itemsPerRow
|
||||
let maxVisibleIndex = min(self.itemCount - 1, (maxVisibleRow + 1) * self.itemsPerRow - 1)
|
||||
let maxVisibleIndex = (maxVisibleRow + 1) * self.itemsPerRow - 1
|
||||
|
||||
if maxVisibleIndex >= minVisibleIndex {
|
||||
return minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||
@@ -266,11 +287,14 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum ItemKey: Hashable {
|
||||
case media(MediaId)
|
||||
case placeholder(Int)
|
||||
}
|
||||
|
||||
fileprivate final class ItemLayer: GifVideoLayer {
|
||||
let item: Item
|
||||
let item: Item?
|
||||
|
||||
private let file: TelegramMediaFile
|
||||
private let placeholderColor: UIColor
|
||||
private var disposable: Disposable?
|
||||
private var fetchDisposable: Disposable?
|
||||
|
||||
@@ -282,60 +306,29 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
private var displayPlaceholder: Bool = false
|
||||
private(set) var displayPlaceholder: Bool = false
|
||||
let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
|
||||
|
||||
init(
|
||||
item: Item,
|
||||
item: Item?,
|
||||
context: AccountContext,
|
||||
groupId: String,
|
||||
attemptSynchronousLoad: Bool,
|
||||
file: TelegramMediaFile,
|
||||
placeholderColor: UIColor
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
|
||||
) {
|
||||
self.item = item
|
||||
self.file = file
|
||||
self.placeholderColor = placeholderColor
|
||||
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||
|
||||
super.init(context: context, file: file, synchronousLoad: attemptSynchronousLoad)
|
||||
super.init(context: context, file: item?.file, synchronousLoad: attemptSynchronousLoad)
|
||||
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
if item == nil {
|
||||
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
|
||||
}
|
||||
|
||||
self.started = { [weak self] in
|
||||
self?.updateDisplayPlaceholder(displayPlaceholder: false)
|
||||
let _ = self
|
||||
//self?.updateDisplayPlaceholder(displayPlaceholder: false, duration: 0.2)
|
||||
}
|
||||
|
||||
/*if attemptSynchronousLoad {
|
||||
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||
self.displayPlaceholder = true
|
||||
|
||||
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: self.size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) {
|
||||
self.contents = image.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable = renderer.add(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||
|
||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||
guard let result = result else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||
writer.finish()
|
||||
return
|
||||
}
|
||||
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||
})
|
||||
|
||||
let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
fetchDisposable.dispose()
|
||||
}
|
||||
})*/
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -363,18 +356,41 @@ public final class GifPagerContentComponent: Component {
|
||||
self.shouldBeAnimating = shouldBePlaying
|
||||
}
|
||||
|
||||
func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) {
|
||||
if self.displayPlaceholder == displayPlaceholder {
|
||||
return
|
||||
}
|
||||
|
||||
self.displayPlaceholder = displayPlaceholder
|
||||
self.onUpdateDisplayPlaceholder(displayPlaceholder, duration)
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemPlaceholderView: UIView {
|
||||
private let shimmerView: PortalSourceView?
|
||||
private var placeholderView: PortalView?
|
||||
|
||||
init(shimmerView: PortalSourceView?) {
|
||||
self.shimmerView = shimmerView
|
||||
self.placeholderView = PortalView()
|
||||
|
||||
if displayPlaceholder {
|
||||
let placeholderColor = self.placeholderColor
|
||||
self.backgroundColor = placeholderColor.cgColor
|
||||
} else {
|
||||
self.backgroundColor = nil
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
if let placeholderView = self.placeholderView, let shimmerView = self.shimmerView {
|
||||
placeholderView.view.clipsToBounds = true
|
||||
self.addSubview(placeholderView.view)
|
||||
shimmerView.addPortal(view: placeholderView)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
if let placeholderView = self.placeholderView {
|
||||
placeholderView.view.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,9 +398,14 @@ public final class GifPagerContentComponent: Component {
|
||||
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
|
||||
}
|
||||
|
||||
private let shimmerHostView: PortalSourceView
|
||||
private let standaloneShimmerEffect: StandaloneShimmerEffect
|
||||
|
||||
private let scrollView: ContentScrollView
|
||||
|
||||
private var visibleItemLayers: [MediaId: ItemLayer] = [:]
|
||||
private let placeholdersContainerView: UIView
|
||||
private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:]
|
||||
private var visibleItemLayers: [ItemKey: ItemLayer] = [:]
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: GifPagerContentComponent?
|
||||
@@ -392,11 +413,21 @@ public final class GifPagerContentComponent: Component {
|
||||
private var theme: PresentationTheme?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
private var currentLoadMoreToken: String?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shimmerHostView = PortalSourceView()
|
||||
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||
|
||||
self.placeholdersContainerView = UIView()
|
||||
|
||||
self.scrollView = ContentScrollView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.shimmerHostView.alpha = 0.0
|
||||
self.addSubview(self.shimmerHostView)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
@@ -409,6 +440,8 @@ public final class GifPagerContentComponent: Component {
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.placeholdersContainerView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
self.useSublayerTransformForActivation = false
|
||||
@@ -450,7 +483,7 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if let component = self.component, let item = self.item(atPoint: recognizer.location(in: self)), let itemView = self.visibleItemLayers[item.file.fileId] {
|
||||
if let component = self.component, let item = self.item(atPoint: recognizer.location(in: self)), let itemView = self.visibleItemLayers[.media(item.file.fileId)] {
|
||||
component.inputInteraction.performItemAction(item, self, self.scrollView.convert(itemView.frame, to: self))
|
||||
}
|
||||
}
|
||||
@@ -473,7 +506,11 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
for (_, itemLayer) in self.visibleItemLayers {
|
||||
if itemLayer.frame.contains(localPoint) {
|
||||
return (itemLayer.item, itemLayer)
|
||||
if let item = itemLayer.item {
|
||||
return (item, itemLayer)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +534,13 @@ public final class GifPagerContentComponent: Component {
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false)
|
||||
|
||||
self.updateScrollingOffset(transition: .immediate)
|
||||
|
||||
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height - 100.0 {
|
||||
if let component = self.component, let loadMoreToken = component.loadMoreToken, self.currentLoadMoreToken != loadMoreToken {
|
||||
self.currentLoadMoreToken = loadMoreToken
|
||||
component.inputInteraction.loadMore(loadMoreToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
@@ -564,44 +608,125 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateVisibleItems(attemptSynchronousLoads: Bool) {
|
||||
guard let component = self.component, let theme = self.theme, let itemLayout = self.itemLayout else {
|
||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
var validIds = Set<MediaId>()
|
||||
var validIds = Set<ItemKey>()
|
||||
|
||||
if let itemRange = itemLayout.visibleItems(for: self.scrollView.bounds) {
|
||||
for index in itemRange.lowerBound ..< itemRange.upperBound {
|
||||
let item = component.items[index]
|
||||
let itemId = item.file.fileId
|
||||
var item: Item?
|
||||
let itemId: ItemKey
|
||||
if index < component.items.count {
|
||||
item = component.items[index]
|
||||
itemId = .media(component.items[index].file.fileId)
|
||||
} else if component.isLoading || component.loadMoreToken != nil {
|
||||
itemId = .placeholder(index)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if !component.isLoading {
|
||||
if let placeholderView = self.visibleItemPlaceholderViews.removeValue(forKey: .placeholder(index)) {
|
||||
self.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
}
|
||||
}
|
||||
|
||||
validIds.insert(itemId)
|
||||
|
||||
let itemFrame = itemLayout.frame(at: index)
|
||||
|
||||
let itemTransition: Transition = .immediate
|
||||
var updateItemLayerPlaceholder = false
|
||||
|
||||
let itemLayer: ItemLayer
|
||||
if let current = self.visibleItemLayers[itemId] {
|
||||
itemLayer = current
|
||||
} else {
|
||||
updateItemLayerPlaceholder = true
|
||||
|
||||
itemLayer = ItemLayer(
|
||||
item: item,
|
||||
context: component.context,
|
||||
groupId: "savedGif",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
file: item.file,
|
||||
placeholderColor: theme.chat.inputMediaPanel.stickersBackgroundColor
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if displayPlaceholder {
|
||||
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
let placeholderView: ItemPlaceholderView
|
||||
if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
placeholderView = current
|
||||
} else {
|
||||
placeholderView = ItemPlaceholderView(shimmerView: strongSelf.shimmerHostView)
|
||||
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
strongSelf.placeholdersContainerView.addSubview(placeholderView)
|
||||
}
|
||||
placeholderView.frame = itemLayer.frame
|
||||
placeholderView.update(size: placeholderView.bounds.size)
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
if duration > 0.0 {
|
||||
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
}
|
||||
|
||||
placeholderView.alpha = 0.0
|
||||
placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak self, weak placeholderView] _ in
|
||||
placeholderView?.removeFromSuperview()
|
||||
self?.updateShimmerIfNeeded()
|
||||
})
|
||||
} else {
|
||||
placeholderView.removeFromSuperview()
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
self.scrollView.layer.addSublayer(itemLayer)
|
||||
self.visibleItemLayers[itemId] = itemLayer
|
||||
}
|
||||
|
||||
itemLayer.frame = itemLayout.frame(at: index)
|
||||
let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
let itemBounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
|
||||
itemTransition.setFrame(layer: itemLayer, frame: itemFrame)
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
|
||||
if let placeholderView = self.visibleItemPlaceholderViews[itemId] {
|
||||
if placeholderView.layer.position != itemPosition || placeholderView.layer.bounds != itemBounds {
|
||||
itemTransition.setFrame(view: placeholderView, frame: itemFrame)
|
||||
placeholderView.update(size: itemFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
if updateItemLayerPlaceholder {
|
||||
if itemLayer.displayPlaceholder {
|
||||
itemLayer.onUpdateDisplayPlaceholder(true, 0.0)
|
||||
} else {
|
||||
itemLayer.onUpdateDisplayPlaceholder(false, 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removedIds: [MediaId] = []
|
||||
var removedIds: [ItemKey] = []
|
||||
for (id, itemLayer) in self.visibleItemLayers {
|
||||
if !validIds.contains(id) {
|
||||
removedIds.append(id)
|
||||
itemLayer.removeFromSuperlayer()
|
||||
|
||||
if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removedIds {
|
||||
@@ -609,13 +734,35 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShimmerIfNeeded() {
|
||||
if self.placeholdersContainerView.subviews.isEmpty {
|
||||
self.standaloneShimmerEffect.layer = nil
|
||||
} else {
|
||||
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: GifPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
var contentReset = false
|
||||
if let previousComponent = self.component, previousComponent.subject != component.subject {
|
||||
contentReset = true
|
||||
self.currentLoadMoreToken = nil
|
||||
}
|
||||
|
||||
let keyboardChildEnvironment = environment[EntityKeyboardChildEnvironment.self].value
|
||||
|
||||
self.component = component
|
||||
self.theme = environment[EntityKeyboardChildEnvironment.self].value.theme
|
||||
self.theme = keyboardChildEnvironment.theme
|
||||
|
||||
let pagerEnvironment = environment[PagerComponentChildEnvironment.self].value
|
||||
self.pagerEnvironment = pagerEnvironment
|
||||
|
||||
transition.setFrame(view: self.shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let shimmerBackgroundColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08)
|
||||
let shimmerForegroundColor = keyboardChildEnvironment.theme.list.itemBlocksBackgroundColor.withMultipliedAlpha(0.15)
|
||||
self.standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
|
||||
|
||||
let itemLayout = ItemLayout(
|
||||
width: availableSize.width,
|
||||
containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top, left: pagerEnvironment.containerInsets.left, bottom: pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right),
|
||||
@@ -631,6 +778,11 @@ public final class GifPagerContentComponent: Component {
|
||||
if self.scrollView.scrollIndicatorInsets != pagerEnvironment.containerInsets {
|
||||
self.scrollView.scrollIndicatorInsets = pagerEnvironment.containerInsets
|
||||
}
|
||||
|
||||
if contentReset {
|
||||
self.scrollView.setContentOffset(CGPoint(), animated: false)
|
||||
}
|
||||
|
||||
self.previousScrollingOffset = self.scrollView.contentOffset.y
|
||||
self.ignoreScrolling = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user