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

This commit is contained in:
Ilya Laktyushin
2022-07-16 15:27:30 +02:00
68 changed files with 2878 additions and 1185 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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()
}
}
}
}

View File

@@ -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

View File

@@ -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