Elevate trending stickers panel item when it contains unread items

This commit is contained in:
Peter Iakovlev 2019-02-12 16:38:23 +04:00
parent a9e5a1c451
commit 2c2ecfe1fc
6 changed files with 84 additions and 16 deletions

View File

@ -665,7 +665,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
#if DEBUG #if false && DEBUG
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: { [weak self] in DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@ -150,9 +150,12 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated)
} }
private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] {
var entries: [ChatMediaInputPanelEntry] = [] var entries: [ChatMediaInputPanelEntry] = []
entries.append(.recentGifs(theme)) entries.append(.recentGifs(theme))
if hasUnreadTrending {
entries.append(.trending(true, theme))
}
if let savedStickers = savedStickers, !savedStickers.items.isEmpty { if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
entries.append(.savedStickers(theme)) entries.append(.savedStickers(theme))
} }
@ -195,7 +198,9 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers
entries.append(.peerSpecific(theme: theme, peer: peer)) entries.append(.peerSpecific(theme: theme, peer: peer))
} }
entries.append(.trending(false, theme)) if !hasUnreadTrending {
entries.append(.trending(false, theme))
}
entries.append(.settings(theme)) entries.append(.settings(theme))
return entries return entries
} }
@ -658,9 +663,20 @@ final class ChatMediaInputNode: ChatInputNode {
peerSpecificPack = .single((nil, .none)) peerSpecificPack = .single((nil, .none))
} }
let hasUnreadTrending = context.account.viewTracker.featuredStickerPacks()
|> map { packs -> Bool in
for pack in packs {
if pack.unread {
return true
}
}
return false
}
|> distinctUntilChanged
let previousView = Atomic<ItemCollectionsView?>(value: nil) let previousView = Atomic<ItemCollectionsView?>(value: nil)
let transitions = combineLatest(itemCollectionsView, peerSpecificPack, self.themeAndStringsPromise.get()) let transitions = combineLatest(itemCollectionsView, peerSpecificPack, hasUnreadTrending, self.themeAndStringsPromise.get())
|> map { viewAndUpdate, peerSpecificPack, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in |> map { viewAndUpdate, peerSpecificPack, hasUnreadTrending, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
let (view, viewUpdate) = viewAndUpdate let (view, viewUpdate) = viewAndUpdate
let previous = previousView.swap(view) let previous = previousView.swap(view)
var update = viewUpdate var update = viewUpdate
@ -678,7 +694,7 @@ final class ChatMediaInputNode: ChatInputNode {
savedStickers = orderedView savedStickers = orderedView
} }
} }
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme) let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, hasUnreadTrending: hasUnreadTrending, theme: theme)
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme) let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme)
let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries))
return (view, preparedChatMediaInputPanelEntryTransition(account: context.account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty) return (view, preparedChatMediaInputPanelEntryTransition(account: context.account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty)

View File

@ -255,8 +255,8 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable {
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0)
inputNodeInteraction.navigateToCollectionId(collectionId) inputNodeInteraction.navigateToCollectionId(collectionId)
}) })
case let .trending(_, theme): case let .trending(elevated, theme):
return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { return ChatMediaInputTrendingItem(inputNodeInteraction: inputNodeInteraction, elevated: elevated, theme: theme, selected: {
let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0) let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
inputNodeInteraction.navigateToCollectionId(collectionId) inputNodeInteraction.navigateToCollectionId(collectionId)
}) })

View File

@ -8,14 +8,16 @@ import Postbox
final class ChatMediaInputTrendingItem: ListViewItem { final class ChatMediaInputTrendingItem: ListViewItem {
let inputNodeInteraction: ChatMediaInputNodeInteraction let inputNodeInteraction: ChatMediaInputNodeInteraction
let selectedItem: () -> Void let selectedItem: () -> Void
let elevated: Bool
let theme: PresentationTheme let theme: PresentationTheme
var selectable: Bool { var selectable: Bool {
return true return true
} }
init(inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping () -> Void) { init(inputNodeInteraction: ChatMediaInputNodeInteraction, elevated: Bool, theme: PresentationTheme, selected: @escaping () -> Void) {
self.inputNodeInteraction = inputNodeInteraction self.inputNodeInteraction = inputNodeInteraction
self.elevated = elevated
self.selectedItem = selected self.selectedItem = selected
self.theme = theme self.theme = theme
} }
@ -26,7 +28,7 @@ final class ChatMediaInputTrendingItem: ListViewItem {
node.contentSize = CGSize(width: 41.0, height: 41.0) node.contentSize = CGSize(width: 41.0, height: 41.0)
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
node.inputNodeInteraction = self.inputNodeInteraction node.inputNodeInteraction = self.inputNodeInteraction
node.updateTheme(theme: self.theme) node.updateTheme(elevated: self.elevated, theme: self.theme)
node.updateIsHighlighted() node.updateIsHighlighted()
node.updateAppearanceTransition(transition: .immediate) node.updateAppearanceTransition(transition: .immediate)
Queue.mainQueue().async { Queue.mainQueue().async {
@ -40,7 +42,7 @@ final class ChatMediaInputTrendingItem: ListViewItem {
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async { Queue.mainQueue().async {
completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in completion(ListViewItemNodeLayout(contentSize: node().contentSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
(node() as? ChatMediaInputTrendingItemNode)?.updateTheme(theme: self.theme) (node() as? ChatMediaInputTrendingItemNode)?.updateTheme(elevated: self.elevated, theme: self.theme)
}) })
} }
} }
@ -62,8 +64,11 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
var currentCollectionId: ItemCollectionId? var currentCollectionId: ItemCollectionId?
var inputNodeInteraction: ChatMediaInputNodeInteraction? var inputNodeInteraction: ChatMediaInputNodeInteraction?
var elevated: Bool = false
var theme: PresentationTheme? var theme: PresentationTheme?
let badgeBackground: ASImageNode
init() { init() {
self.highlightNode = ASImageNode() self.highlightNode = ASImageNode()
self.highlightNode.isLayerBacked = true self.highlightNode.isLayerBacked = true
@ -74,6 +79,11 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
self.imageNode.contentMode = .center self.imageNode.contentMode = .center
self.imageNode.contentsScale = UIScreenScale self.imageNode.contentsScale = UIScreenScale
self.badgeBackground = ASImageNode()
self.badgeBackground.displaysAsynchronously = false
self.badgeBackground.displayWithoutProcessing = true
self.badgeBackground.isHidden = true
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize) self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
@ -82,22 +92,34 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
self.addSubnode(self.highlightNode) self.addSubnode(self.highlightNode)
self.addSubnode(self.imageNode) self.addSubnode(self.imageNode)
self.addSubnode(self.badgeBackground)
self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0) self.currentCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)
let imageSize = CGSize(width: 26.0, height: 26.0)
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
} }
deinit { deinit {
} }
func updateTheme(theme: PresentationTheme) { func updateTheme(elevated: Bool, theme: PresentationTheme) {
if self.theme !== theme { if self.theme !== theme {
self.theme = theme self.theme = theme
self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme) self.highlightNode.image = PresentationResourcesChat.chatMediaInputPanelHighlightedIconImage(theme)
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme) self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingIconImage(theme)
self.badgeBackground.image = generateFilledCircleImage(diameter: 16.0, color: theme.chat.inputPanel.mediaRecordingDotColor)
let imageSize = CGSize(width: 26.0, height: 26.0)
let imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
self.imageNode.frame = imageFrame
if let image = self.badgeBackground.image {
self.badgeBackground.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - image.size.width + 2.0, y: imageFrame.maxY - image.size.width + 3.0), size: image.size)
}
}
if self.elevated != elevated {
self.elevated = elevated
self.badgeBackground.isHidden = !self.elevated
} }
} }
@ -112,5 +134,13 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition) transition.updateSublayerTransformScale(node: self, scale: inputNodeInteraction.appearanceTransition)
} }
} }
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
} }

View File

@ -59,6 +59,9 @@ private final class TrendingPaneEntry: Identifiable, Comparable {
if lhs.installed != rhs.installed { if lhs.installed != rhs.installed {
return false return false
} }
if lhs.unread != rhs.unread {
return false
}
return true return true
} }

View File

@ -99,6 +99,24 @@ class MediaInputPaneTrendingItemNode: ListViewItemNode {
private var item: MediaInputPaneTrendingItem? private var item: MediaInputPaneTrendingItem?
private let preloadDisposable = MetaDisposable() private let preloadDisposable = MetaDisposable()
private let readDisposable = MetaDisposable()
override var visibility: ListViewItemNodeVisibility {
didSet {
if self.visibility != oldValue {
if self.visibility == .visible {
if let item = self.item, item.unread {
self.readDisposable.set((
markFeaturedStickerPacksAsSeenInteractively(postbox: item.account.postbox, ids: [item.info.id])
|> delay(1.0, queue: .mainQueue())
).start())
}
} else {
self.readDisposable.set(nil)
}
}
}
}
init() { init() {
self.titleNode = TextNode() self.titleNode = TextNode()
@ -160,6 +178,7 @@ class MediaInputPaneTrendingItemNode: ListViewItemNode {
deinit { deinit {
self.preloadDisposable.dispose() self.preloadDisposable.dispose()
self.readDisposable.dispose()
} }
override func didLoad() { override func didLoad() {
@ -236,7 +255,7 @@ class MediaInputPaneTrendingItemNode: ListViewItemNode {
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
strongSelf.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: 23.0), size: descriptionLayout.size) strongSelf.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: 23.0), size: descriptionLayout.size)
if false && item.unread { if item.unread {
strongSelf.unreadNode.isHidden = false strongSelf.unreadNode.isHidden = false
} else { } else {
strongSelf.unreadNode.isHidden = true strongSelf.unreadNode.isHidden = true