diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 045a86eabc..ad3346987d 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -116,13 +116,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch if case let .search(search) = source { switch search { case .recentPeers: - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + break + /*items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in let _ = (context.engine.peers.removeRecentPeer(peerId: peerId) |> deliverOnMainQueue).startStandalone(completed: { f(.default) }) }))) - items.append(.separator) + items.append(.separator)*/ case .recentSearch: items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId) @@ -535,6 +536,23 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } f(.default) }))) + } else if case let .search(search) = source { + switch search { + case .recentPeers, .search: + if peerGroup != nil { + if !items.isEmpty { + items.append(.separator) + } + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + if let chatListController = chatListController { + chatListController.deletePeerChat(peerId: peerId, joined: joined) + } + f(.default) + }))) + } + default: + break + } } } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 03ad7bd696..9ac0d75f97 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -544,6 +544,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { } private struct ChatListFilterPresetControllerState: Equatable { + var isExisting: Bool var name: ChatFolderTitle var changedName: Bool var nameInputMode: ListComposePollOptionComponent.InputMode = .keyboard @@ -564,6 +565,10 @@ private struct ChatListFilterPresetControllerState: Equatable { return false } + if self.isExisting { + return true + } + let defaultCategories: ChatListFilterPeerCategories = .all let defaultExcludeArchived = true let defaultExcludeMuted = false @@ -1388,6 +1393,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi initialName = ChatFolderTitle(text: "", entities: [], enableAnimations: true) } var initialState = ChatListFilterPresetControllerState( + isExisting: initialPreset?.id != nil, name: initialName, changedName: initialPreset != nil, color: initialPreset?.data?.color, diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index b3c32c75b6..428db40881 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2048,11 +2048,31 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return result } + let updatedLocalPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased()) + |> mapToSignal { peers -> Signal<[EngineRenderedPeer], NoError> in + return context.engine.data.subscribe( + EngineDataMap(peers.map { peer in + return TelegramEngine.EngineData.Item.Messages.ChatListIndex(id: peer.peerId) + }) + ) + |> map { chatListIndices -> [EngineRenderedPeer] in + return peers.filter { peer in + if peer.peerId.namespace == Namespaces.Peer.CloudUser || peer.peerId.namespace == Namespaces.Peer.SecretChat { + return true + } + if let maybeIndex = chatListIndices[peer.peerId], maybeIndex != nil { + return true + } + return false + } + } + } + foundLocalPeers = combineLatest( - context.engine.contacts.searchLocalPeers(query: query.lowercased()), + updatedLocalPeers, fixedOrRemovedRecentlySearchedPeers ) - |> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional], [EnginePeer.Id: Int], [EngineRenderedPeer], Set, EngineGlobalNotificationSettings), NoError> in + |> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional], [EnginePeer.Id: TelegramEngine.EngineData.Item.Messages.PeerUnreadState.Result], [EngineRenderedPeer], Set, EngineGlobalNotificationSettings), NoError> in let recentlySearched = allRecentlySearched.filter { peer in guard let peer = peer.peer.peer else { return false @@ -2083,8 +2103,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } ), EngineDataMap( - peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in - return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId) + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadState in + return TelegramEngine.EngineData.Item.Messages.PeerUnreadState(id: peerId) } ), TelegramEngine.EngineData.Item.NotificationSettings.Global() @@ -2118,8 +2138,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } let unreadCount = unreadCounts[peer.peerId] - if let unreadCount = unreadCount, unreadCount > 0 { - unread[peer.peerId] = (Int32(unreadCount), isMuted) + if let unreadCount = unreadCount, (unreadCount.count > 0 || unreadCount.isMarkedUnread) { + unread[peer.peerId] = (Int32(unreadCount.count), isMuted) } } return (peers: peers, unread: unread, recentlySearchedPeerIds: recentlySearchedPeerIds) diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 53dcb81b79..202a3ddcc2 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -955,6 +955,33 @@ public struct ComponentTransition { } } + public func setShadowPath(layer: CALayer, path: CGPath, completion: ((Bool) -> Void)? = nil) { + switch self.animation { + case .none: + layer.shadowPath = path + completion?(true) + case let .curve(duration, curve): + if let previousPath = layer.shadowPath, previousPath != path { + layer.animate( + from: previousPath, + to: path, + keyPath: "shadowPath", + duration: duration, + delay: 0.0, + curve: curve, + removeOnCompletion: true, + additive: false, + completion: completion + ) + layer.shadowPath = path + } else { + layer.shadowPath = path + completion?(true) + } + } + } + + public func setShapeLayerPath(layer: CAShapeLayer, path: CGPath, completion: ((Bool) -> Void)? = nil) { switch self.animation { case .none: diff --git a/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift b/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift index 8849f4785f..07451ea341 100644 --- a/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift +++ b/submodules/Components/BlurredBackgroundComponent/Sources/BlurredBackgroundComponent.swift @@ -37,7 +37,7 @@ public final class BlurredBackgroundComponent: Component { private var vibrancyEffectView: UIVisualEffectView? public func update(component: BlurredBackgroundComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition) + self.updateColor(color: component.color, forceKeepBlur: true, transition: transition.containedViewLayoutTransition) self.update(size: availableSize, cornerRadius: component.cornerRadius, transition: transition.containedViewLayoutTransition) diff --git a/submodules/Postbox/Sources/UnreadMessageCountsView.swift b/submodules/Postbox/Sources/UnreadMessageCountsView.swift index 9ec6c8c036..6818122a3b 100644 --- a/submodules/Postbox/Sources/UnreadMessageCountsView.swift +++ b/submodules/Postbox/Sources/UnreadMessageCountsView.swift @@ -208,6 +208,20 @@ public final class UnreadMessageCountsView: PostboxView { } return nil } + + public func countOrUnread(for item: UnreadMessageCountsItem) -> (Int32, Bool)? { + for entry in self.entries { + switch entry { + case .total, .totalInGroup: + break + case let .peer(peerId, state): + if case .peer(peerId, _) = item { + return (state?.count ?? 0, state?.markedUnread ?? false) + } + } + } + return nil + } } final class MutableCombinedReadStateView: MutablePostboxView { diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 79678e84a2..4a9bc49f2d 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -116,6 +116,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/BackButtonComponent", "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/Components/BlurredBackgroundComponent", "//submodules/DirectMediaImageCache", "//submodules/FastBlur", ], diff --git a/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift new file mode 100644 index 0000000000..73cef654bd --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/VideoChatEncryptionKeyComponent.swift @@ -0,0 +1,377 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MultilineTextComponent +import BalancedTextComponent +import TelegramPresentationData + +final class VideoChatEncryptionKeyComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let emoji: [String] + let isExpanded: Bool + let tapAction: () -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + emoji: [String], + isExpanded: Bool, + tapAction: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.emoji = emoji + self.isExpanded = isExpanded + self.tapAction = tapAction + } + + static func ==(lhs: VideoChatEncryptionKeyComponent, rhs: VideoChatEncryptionKeyComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.emoji != rhs.emoji { + return false + } + if lhs.isExpanded != rhs.isExpanded { + return false + } + return true + } + + final class View: UIView { + private let containerView: UIView + private var emojiItems: [ComponentView] = [] + private let background = ComponentView() + private let backgroundShadowLayer = SimpleLayer() + private let collapsedText = ComponentView() + private let expandedText = ComponentView() + private let expandedSeparatorLayer = SimpleLayer() + private let expandedButtonText = ComponentView() + + private var component: VideoChatEncryptionKeyComponent? + private var isUpdating: Bool = false + + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? + + override init(frame: CGRect) { + self.containerView = UIView() + self.containerView.clipsToBounds = true + + super.init(frame: frame) + + self.addSubview(self.containerView) + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.addGestureRecognizer(tapRecognizer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + guard let component = self.component else { + return + } + if case .ended = recognizer.state { + component.tapAction() + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.containerView.frame.contains(point) { + return self + } else { + return nil + } + } + + func update(component: VideoChatEncryptionKeyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.component = component + + let alphaTransition: ComponentTransition + if transition.animation.isImmediate { + alphaTransition = .immediate + } else { + alphaTransition = .easeInOut(duration: 0.25) + } + + let collapsedSideInset: CGFloat = 7.0 + let collapsedVerticalInset: CGFloat = 8.0 + let collapsedEmojiSpacing: CGFloat = 0.0 + let collapsedEmojiTextSpacing: CGFloat = 5.0 + + let expandedTopInset: CGFloat = 8.0 + let expandedSideInset: CGFloat = 14.0 + var expandedEmojiSpacing: CGFloat = 10.0 + let expandedEmojiTextSpacing: CGFloat = 10.0 + let expandedTextButtonSpacing: CGFloat = 10.0 + let expandedButtonTopInset: CGFloat = 12.0 + let expandedButtonBottomInset: CGFloat = 13.0 + + let emojiItemSizes = (0 ..< component.emoji.count).map { i -> CGSize in + let emojiItem: ComponentView + if self.emojiItems.count > i { + emojiItem = self.emojiItems[i] + } else { + emojiItem = ComponentView() + self.emojiItems.append(emojiItem) + } + return emojiItem.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.emoji[i], font: Font.regular(40.0), textColor: .white)) + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 200.0) + ) + } + + //TODO:localize + let collapsedTextSize = self.collapsedText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "End-to-end encrypted", font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: 1000.0, height: 1000.0) + ) + + let expandedTextSize = self.expandedText.update( + transition: .immediate, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString(string: "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves.", font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)), + maximumNumberOfLines: 0, + lineSpacing: 0.3 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0) + ) + + let expandedButtonTextSize = self.expandedButtonText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Close", font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0) + ) + + let collapsedEmojiFactor: CGFloat = 20.0 / 40.0 + let collapsedEmojiSizes = emojiItemSizes.map { CGSize(width: floor($0.width * collapsedEmojiFactor), height: floor($0.height * collapsedEmojiFactor)) } + + let collapsedSize = CGSize(width: collapsedTextSize.width + collapsedSideInset * 2.0 + collapsedEmojiTextSpacing * 2.0 + collapsedEmojiSpacing + collapsedEmojiSizes.reduce(into: 0.0, { $0 += $1.width }), height: collapsedTextSize.height + collapsedVerticalInset * 2.0) + + var expandedEmojiWidth = expandedEmojiSpacing * CGFloat(self.emojiItems.count - 1) + emojiItemSizes.reduce(into: 0.0, { $0 += $1.width }) + + var expandedSize = CGSize(width: expandedSideInset * 2.0, height: 0.0) + + expandedSize.width += max(expandedTextSize.width, expandedEmojiWidth) + + expandedSize.height += expandedTopInset + expandedSize.height += emojiItemSizes[0].height + expandedSize.height += expandedEmojiTextSpacing + expandedSize.height += expandedTextSize.height + expandedSize.height += expandedTextButtonSpacing + expandedSize.height += expandedButtonTopInset + expandedSize.height += expandedButtonTextSize.height + expandedSize.height += expandedButtonBottomInset + + if expandedEmojiWidth < expandedSize.width - expandedSideInset * 2.0 { + expandedEmojiWidth = expandedSize.width - expandedSideInset * 2.0 + + let cleanEmojiWidth = emojiItemSizes.reduce(into: 0.0, { $0 += $1.width }) + expandedEmojiSpacing = floorToScreenPixels((expandedEmojiWidth - cleanEmojiWidth) / CGFloat(self.emojiItems.count - 1)) + expandedEmojiSpacing = min(expandedEmojiSpacing, 24.0) + expandedEmojiWidth = expandedEmojiSpacing * CGFloat(self.emojiItems.count - 1) + emojiItemSizes.reduce(into: 0.0, { $0 += $1.width }) + } + + let backgroundSize = component.isExpanded ? expandedSize : collapsedSize + let backgroundCornerRadius: CGFloat = component.isExpanded ? 10.0 : collapsedSize.height * 0.5 + + let _ = self.background.update( + transition: transition, + component: AnyComponent(FilledRoundedRectangleComponent( + color: component.theme.list.itemBlocksBackgroundColor, + cornerRadius: .value(backgroundCornerRadius), smoothCorners: false + )), + environment: {}, + containerSize: backgroundSize + ) + let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) + + if self.backgroundShadowLayer.superlayer == nil { + self.backgroundShadowLayer.backgroundColor = UIColor.clear.cgColor + self.containerView.layer.addSublayer(self.backgroundShadowLayer) + } + self.backgroundShadowLayer.shadowOpacity = 0.3 + self.backgroundShadowLayer.shadowColor = UIColor.black.cgColor + self.backgroundShadowLayer.shadowRadius = 5.0 + self.backgroundShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0) + alphaTransition.setAlpha(layer: self.backgroundShadowLayer, alpha: component.isExpanded ? 1.0 : 0.0) + + transition.setFrame(layer: self.backgroundShadowLayer, frame: backgroundFrame) + transition.setCornerRadius(layer: self.backgroundShadowLayer, cornerRadius: backgroundCornerRadius) + transition.setShadowPath(layer: self.backgroundShadowLayer, path: UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: backgroundFrame.size), cornerRadius: backgroundCornerRadius).cgPath) + + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.containerView.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + var collapsedEmojiLeftOffset = collapsedSideInset + var collapsedEmojiRightOffset = collapsedSize.width - collapsedSideInset + + for i in 0 ..< self.emojiItems.count { + let mappedIndex: Int + if i < 2 { + mappedIndex = i + } else { + mappedIndex = self.emojiItems.count - (i - 1) + } + + var collapsedItemFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((collapsedSize.height - collapsedEmojiSizes[mappedIndex].height) * 0.5)), size: collapsedEmojiSizes[mappedIndex]) + if i < 2 { + if mappedIndex != 0 { + collapsedEmojiLeftOffset += collapsedEmojiSpacing + } + collapsedItemFrame.origin.x = collapsedEmojiLeftOffset + collapsedEmojiLeftOffset += collapsedEmojiSizes[mappedIndex].width + } else { + if mappedIndex != 0 { + collapsedEmojiRightOffset -= collapsedEmojiSpacing + } + collapsedItemFrame.origin.x = collapsedEmojiRightOffset - collapsedEmojiSizes[mappedIndex].width + collapsedEmojiRightOffset -= collapsedEmojiSizes[mappedIndex].width + } + + var expandedItemFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - expandedEmojiWidth) * 0.5), y: expandedTopInset), size: emojiItemSizes[mappedIndex]) + expandedItemFrame.origin.x += CGFloat(mappedIndex) * (emojiItemSizes[0].width + expandedEmojiSpacing) + + let itemFrame: CGRect + if component.isExpanded { + itemFrame = expandedItemFrame + } else { + itemFrame = collapsedItemFrame + } + + if let itemView = self.emojiItems[mappedIndex].view { + if itemView.superview == nil { + self.containerView.addSubview(itemView) + } + transition.setPosition(view: itemView, position: itemFrame.center) + itemView.bounds = CGRect(origin: CGPoint(), size: emojiItemSizes[mappedIndex]) + transition.setScale(view: itemView, scale: itemFrame.height / emojiItemSizes[mappedIndex].height) + } + } + + var collapsedTextFrame = CGRect(origin: CGPoint(x: collapsedEmojiLeftOffset + collapsedEmojiTextSpacing, y: floor((collapsedSize.height - collapsedTextSize.height) * 0.5)), size: collapsedTextSize) + var expandedTextFrame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - expandedTextSize.width) * 0.5), y: expandedTopInset + emojiItemSizes[0].height + expandedEmojiTextSpacing), size: expandedTextSize) + + if component.isExpanded { + collapsedTextFrame.origin = expandedTextFrame.origin + } else { + expandedTextFrame.origin = collapsedTextFrame.origin + } + + if let collapsedTextView = self.collapsedText.view { + if collapsedTextView.superview == nil { + collapsedTextView.layer.anchorPoint = CGPoint() + self.containerView.addSubview(collapsedTextView) + } else { + if collapsedTextView.alpha != (component.isExpanded ? 0.0 : 1.0) { + if let blurFilter = CALayer.blur() { + let maxBlur: CGFloat = 8.0 + if !component.isExpanded { + blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius") + collapsedTextView.layer.filters = [blurFilter] + collapsedTextView.layer.animate(from: maxBlur as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak collapsedTextView] flag in + if flag, let collapsedTextView { + collapsedTextView.layer.filters = nil + } + }) + } else { + blurFilter.setValue(maxBlur as NSNumber, forKey: "inputRadius") + collapsedTextView.layer.filters = [blurFilter] + collapsedTextView.layer.animate(from: 0.0 as NSNumber, to: maxBlur as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: true) + } + } + } + } + transition.setPosition(view: collapsedTextView, position: collapsedTextFrame.origin) + collapsedTextView.bounds = CGRect(origin: CGPoint(), size: collapsedTextFrame.size) + alphaTransition.setAlpha(view: collapsedTextView, alpha: component.isExpanded ? 0.0 : 1.0) + } + + if let expandedTextView = self.expandedText.view { + if expandedTextView.superview == nil { + expandedTextView.layer.anchorPoint = CGPoint() + self.containerView.addSubview(expandedTextView) + } else { + if expandedTextView.alpha != (component.isExpanded ? 1.0 : 0.0) { + if let blurFilter = CALayer.blur() { + let maxBlur: CGFloat = 8.0 + if component.isExpanded { + blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius") + expandedTextView.layer.filters = [blurFilter] + expandedTextView.layer.animate(from: maxBlur as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak expandedTextView] flag in + if flag, let expandedTextView { + expandedTextView.layer.filters = nil + } + }) + } else { + blurFilter.setValue(maxBlur as NSNumber, forKey: "inputRadius") + expandedTextView.layer.filters = [blurFilter] + expandedTextView.layer.animate(from: 0.0 as NSNumber, to: maxBlur as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: true) + } + } + } + } + transition.setPosition(view: expandedTextView, position: expandedTextFrame.origin) + expandedTextView.bounds = CGRect(origin: CGPoint(), size: expandedTextFrame.size) + alphaTransition.setAlpha(view: expandedTextView, alpha: component.isExpanded ? 1.0 : 0.0) + } + + let expandedButtonOffset: CGFloat = component.isExpanded ? 0.0 : (expandedButtonBottomInset + expandedButtonTextSize.height + expandedButtonTopInset) + + if self.expandedSeparatorLayer.superlayer == nil { + self.containerView.layer.addSublayer(self.expandedSeparatorLayer) + } + self.expandedSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor + transition.setFrame(layer: self.expandedSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: backgroundSize.height - expandedButtonBottomInset - expandedButtonTextSize.height - expandedButtonTopInset + expandedButtonOffset), size: CGSize(width: backgroundSize.width, height: UIScreenPixel))) + alphaTransition.setAlpha(layer: self.expandedSeparatorLayer, alpha: component.isExpanded ? 1.0 : 0.0) + + if let expandedButtonTextView = self.expandedButtonText.view { + if expandedButtonTextView.superview == nil { + self.containerView.addSubview(expandedButtonTextView) + } + transition.setFrame(view: expandedButtonTextView, frame: CGRect(origin: CGPoint(x: floor((backgroundSize.width - expandedButtonTextSize.width) * 0.5), y: backgroundSize.height - expandedButtonBottomInset - expandedButtonTextSize.height + expandedButtonOffset), size: expandedButtonTextSize)) + alphaTransition.setAlpha(view: expandedButtonTextView, alpha: component.isExpanded ? 1.0 : 0.0) + } + + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)) + + return CGSize(width: backgroundSize.width, height: collapsedSize.height) + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index ad86d48efe..05151e90e8 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -23,6 +23,7 @@ import AvatarNode import TelegramAudio import LegacyComponents import TooltipUI +import BlurredBackgroundComponent extension VideoChatCall { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { @@ -219,6 +220,9 @@ final class VideoChatScreenComponent: Component { let navigationLeftButton = ComponentView() let navigationRightButton = ComponentView() var navigationSidebarButton: ComponentView? + var encryptionKeyBackground: ComponentView? + var encryptionKey: ComponentView? + var isEncryptionKeyExpanded: Bool = false let videoButton = ComponentView() let leaveButton = ComponentView() @@ -406,6 +410,12 @@ final class VideoChatScreenComponent: Component { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let encryptionKeyBackgroundView = self.encryptionKeyBackground?.view, let _ = encryptionKeyBackgroundView.hitTest(self.convert(point, to: encryptionKeyBackgroundView), with: event) { + if let encryptionKeyView = self.encryptionKey?.view { + return encryptionKeyView + } + } + guard let result = super.hitTest(point, with: event) else { return nil } @@ -1744,7 +1754,7 @@ final class VideoChatScreenComponent: Component { let topInset: CGFloat = environment.statusBarHeight + 2.0 let navigationBarHeight: CGFloat = 61.0 - let navigationHeight = topInset + navigationBarHeight + var navigationHeight = topInset + navigationBarHeight let navigationButtonAreaWidth: CGFloat = 40.0 let navigationButtonDiameter: CGFloat = 28.0 @@ -1950,6 +1960,52 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } + var encryptionKeyFrame: CGRect? + if component.initialCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug { + navigationHeight -= 2.0 + let encryptionKey: ComponentView + var encryptionKeyTransition = transition + if let current = self.encryptionKey { + encryptionKey = current + } else { + encryptionKeyTransition = encryptionKeyTransition.withAnimation(.none) + encryptionKey = ComponentView() + self.encryptionKey = encryptionKey + } + + let encryptionKeySize = encryptionKey.update( + transition: encryptionKeyTransition, + component: AnyComponent(VideoChatEncryptionKeyComponent( + theme: environment.theme, + strings: environment.strings, + emoji: ["๐Ÿ‘Œ", "๐Ÿงก", "๐ŸŒน", "๐Ÿคท"], + isExpanded: self.isEncryptionKeyExpanded, + tapAction: { [weak self] in + guard let self else { + return + } + self.isEncryptionKeyExpanded = !self.isEncryptionKeyExpanded + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + )), + environment: {}, + containerSize: CGSize(width: min(400.0, availableSize.width - sideInset * 2.0 - 16.0 * 2.0), height: 10000.0) + ) + let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: floor((availableSize.width - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize) + encryptionKeyFrame = encryptionKeyFrameValue + + navigationHeight += encryptionKeySize.height + navigationHeight += 16.0 + } else if let encryptionKey = self.encryptionKey { + self.encryptionKey = nil + encryptionKey.view?.removeFromSuperview() + + self.encryptionKeyBackground?.view?.removeFromSuperview() + self.encryptionKeyBackground = nil + } + let areButtonsCollapsed: Bool let mainColumnWidth: CGFloat let mainColumnSideInset: CGFloat @@ -2226,6 +2282,51 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: participantsView, alpha: participantsAlpha) } + if let encryptionKeyView = self.encryptionKey?.view, let encryptionKeyFrame { + if encryptionKeyView.superview == nil { + self.containerView.addSubview(encryptionKeyView) + } + transition.setFrame(view: encryptionKeyView, frame: encryptionKeyFrame) + alphaTransition.setAlpha(view: encryptionKeyView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) + + if self.isEncryptionKeyExpanded { + let encryptionKeyBackground: ComponentView + var encryptionKeyBackgroundTransition = transition + if let current = self.encryptionKeyBackground { + encryptionKeyBackground = current + } else { + encryptionKeyBackgroundTransition = encryptionKeyBackgroundTransition.withAnimation(.none) + encryptionKeyBackground = ComponentView() + self.encryptionKeyBackground = encryptionKeyBackground + } + let _ = encryptionKeyBackground.update( + transition: encryptionKeyBackgroundTransition, + component: AnyComponent(BlurredBackgroundComponent( + color: .clear, + tintContainerView: nil, + cornerRadius: 0.0 + )), + environment: {}, + containerSize: availableSize + ) + if let encryptionKeyBackgroundView = encryptionKeyBackground.view { + if encryptionKeyBackgroundView.superview == nil { + self.containerView.insertSubview(encryptionKeyBackgroundView, belowSubview: encryptionKeyView) + encryptionKeyBackgroundView.alpha = 0.0 + } + alphaTransition.setAlpha(view: encryptionKeyBackgroundView, alpha: 1.0) + encryptionKeyBackgroundTransition.setFrame(view: encryptionKeyBackgroundView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + } else if let encryptionKeyBackground = self.encryptionKeyBackground { + self.encryptionKeyBackground = nil + if let encryptionKeyBackgroundView = encryptionKeyBackground.view { + alphaTransition.setAlpha(view: encryptionKeyBackgroundView, alpha: 0.0, completion: { [weak encryptionKeyBackgroundView] _ in + encryptionKeyBackgroundView?.removeFromSuperview() + }) + } + } + } + if let callState = self.callState, let scheduleTimestamp = callState.scheduleTimestamp { let scheduleInfo: ComponentView var scheduleInfoTransition = transition diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift index 47ee37c16b..daa7d28f07 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift @@ -226,6 +226,42 @@ public extension TelegramEngine.EngineData.Item { return Int(view.count(for: .peer(id: self.id, handleThreads: true)) ?? 0) } } + + public struct PeerUnreadState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public struct Result: Equatable { + public var count: Int + public var isMarkedUnread: Bool + + public init(count: Int, isMarkedUnread: Bool) { + self.count = count + self.isMarkedUnread = isMarkedUnread + } + } + + fileprivate let id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + var key: PostboxViewKey { + return .unreadCounts(items: [.peer(id: self.id, handleThreads: true)]) + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? UnreadMessageCountsView else { + preconditionFailure() + } + + if let (value, isUnread) = view.countOrUnread(for: .peer(id: self.id, handleThreads: true)) { + return Result(count: Int(value), isMarkedUnread: isUnread) + } + return Result(count: 0, isMarkedUnread: false) + } + } public struct TotalReadCounters: TelegramEngineDataItem, PostboxViewDataItem { public typealias Result = EngineTotalReadCounters diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift index 554c91f901..d74f0fe857 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift @@ -17,7 +17,7 @@ public enum CreateChannelMode { case supergroup(isForum: Bool) } -private func createChannel(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, mode: CreateChannelMode, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal { +private func createChannel(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, mode: CreateChannelMode, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32?) -> Signal { return postbox.transaction { transaction -> Signal in var flags: Int32 = 0 switch mode { @@ -43,7 +43,11 @@ private func createChannel(postbox: Postbox, network: Network, stateManager: Acc transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers) - return network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: nil), automaticFloodWait: false) + if ttlPeriod != nil { + flags |= (1 << 4) + } + + return network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address, ttlPeriod: ttlPeriod), automaticFloodWait: false) |> mapError { error -> CreateChannelError in if error.errorCode == 406 { return .serverProvided(error.errorDescription) @@ -91,11 +95,11 @@ private func createChannel(postbox: Postbox, network: Network, stateManager: Acc } func _internal_createChannel(account: Account, title: String, description: String?, username: String?) -> Signal { - return createChannel(postbox: account.postbox, network: account.network, stateManager: account.stateManager, title: title, description: description, username: nil, mode: .channel) + return createChannel(postbox: account.postbox, network: account.network, stateManager: account.stateManager, title: title, description: description, username: nil, mode: .channel, ttlPeriod: nil) } -public func _internal_createSupergroup(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, isForum: Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal { - return createChannel(postbox: postbox, network: network, stateManager: stateManager, title: title, description: description, username: username, mode: .supergroup(isForum: isForum), location: location, isForHistoryImport: isForHistoryImport) +public func _internal_createSupergroup(postbox: Postbox, network: Network, stateManager: AccountStateManager, title: String, description: String?, username: String?, isForum: Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32? = nil) -> Signal { + return createChannel(postbox: postbox, network: network, stateManager: stateManager, title: title, description: description, username: username, mode: .supergroup(isForum: isForum), location: location, isForHistoryImport: isForHistoryImport, ttlPeriod: ttlPeriod) } public enum DeleteChannelError { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 9edca1bc39..b5696bd678 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -466,8 +466,8 @@ public extension TelegramEngine { return _internal_createChannel(account: self.account, title: title, description: description, username: username) } - public func createSupergroup(title: String, description: String?, username: String? = nil, isForum: Bool = false, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal { - return _internal_createSupergroup(postbox: self.account.postbox, network: self.account.network, stateManager: account.stateManager, title: title, description: description, username: username, isForum: isForum, location: location, isForHistoryImport: isForHistoryImport) + public func createSupergroup(title: String, description: String?, username: String? = nil, isForum: Bool = false, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false, ttlPeriod: Int32? = nil) -> Signal { + return _internal_createSupergroup(postbox: self.account.postbox, network: self.account.network, stateManager: account.stateManager, title: title, description: description, username: username, isForum: isForum, location: location, isForHistoryImport: isForHistoryImport, ttlPeriod: ttlPeriod) } public func deleteChannel(peerId: PeerId) -> Signal { diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 01a4e252f0..16994577f6 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -467,8 +467,9 @@ public final class MessageInputPanelComponent: Component { private let gradientView: UIImageView private let bottomGradientView: UIView - private let placeholder = ComponentView() - private let vibrancyPlaceholder = ComponentView() + private var currentPlaceholderType: Bool? + private var placeholder = ComponentView() + private var vibrancyPlaceholder = ComponentView() private let counter = ComponentView() private var header: ComponentView? @@ -868,7 +869,17 @@ public final class MessageInputPanelComponent: Component { let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate let placeholderSize: CGSize + if case let .plain(string) = component.placeholder, string.contains("#") { + let placeholderType = false + if let currentPlaceholderType = self.currentPlaceholderType, currentPlaceholderType != placeholderType { + self.placeholder.view?.removeFromSuperview() + self.placeholder = ComponentView() + + self.vibrancyPlaceholder.view?.removeFromSuperview() + self.vibrancyPlaceholder = ComponentView() + } + let attributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.4)) if let range = attributedPlaceholder.string.range(of: "#") { attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: attributedPlaceholder.string)) @@ -897,6 +908,15 @@ public final class MessageInputPanelComponent: Component { containerSize: availableTextFieldSize ) } else { + let placeholderType = true + if let currentPlaceholderType = self.currentPlaceholderType, currentPlaceholderType != placeholderType { + self.placeholder.view?.removeFromSuperview() + self.placeholder = ComponentView() + + self.vibrancyPlaceholder.view?.removeFromSuperview() + self.vibrancyPlaceholder = ComponentView() + } + var placeholderItems: [AnimatedTextComponent.Item] = [] switch component.placeholder { case let .plain(string): diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index dad267cb29..6a04bb0809 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -155,6 +155,8 @@ swift_library( "//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/PeerInfo/VerifyAlertController", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", + "//submodules/TelegramUI/Components/BatchVideoRendering", + "//submodules/TelegramUI/Components/GifVideoLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift index a360848d8a..3fb21fe5ed 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift @@ -17,106 +17,8 @@ import SoftwareVideo import ChatControllerInteraction import PeerInfoVisualMediaPaneNode import PeerInfoPaneNode - -private final class FrameSequenceThumbnailNode: ASDisplayNode { - private let context: AccountContext - private let file: FileMediaReference - private let imageNode: ASImageNode - - private var isPlaying: Bool = false - private var isPlayingInternal: Bool = false - - private var frames: [Int: UIImage] = [:] - - private var frameTimes: [Double] = [] - private var sources: [UniversalSoftwareVideoSource] = [] - private var disposables: [Int: Disposable] = [:] - - private var currentFrameIndex: Int = 0 - private var timer: SwiftSignalKit.Timer? - - init( - context: AccountContext, - userLocation: MediaResourceUserLocation, - file: FileMediaReference - ) { - self.context = context - self.file = file - - self.imageNode = ASImageNode() - self.imageNode.isUserInteractionEnabled = false - self.imageNode.contentMode = .scaleAspectFill - self.imageNode.clipsToBounds = true - - if let duration = file.media.duration { - let frameCount = 5 - let frameInterval: Double = Double(duration) / Double(frameCount) - for i in 0 ..< frameCount { - self.frameTimes.append(Double(i) * frameInterval) - } - } - - super.init() - - self.addSubnode(self.imageNode) - - for i in 0 ..< self.frameTimes.count { - let framePts = self.frameTimes[i] - let index = i - - let source = UniversalSoftwareVideoSource( - mediaBox: self.context.account.postbox.mediaBox, - source: .file( - userLocation: userLocation, - userContentType: .other, - fileReference: self.file - ), - automaticallyFetchHeader: true - ) - self.sources.append(source) - self.disposables[index] = (source.takeFrame(at: framePts) - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - if case let .image(image) = result { - if let image = image { - strongSelf.frames[index] = image - } - } - }) - } - } - - deinit { - for (_, disposable) in self.disposables { - disposable.dispose() - } - self.timer?.invalidate() - } - - func updateIsPlaying(_ isPlaying: Bool) { - if self.isPlaying == isPlaying { - return - } - self.isPlaying = isPlaying - } - - func updateLayout(size: CGSize) { - self.imageNode.frame = CGRect(origin: CGPoint(), size: size) - } - - func tick() { - let isPlayingInternal = self.isPlaying && self.frames.count == self.frameTimes.count - if isPlayingInternal { - self.currentFrameIndex = (self.currentFrameIndex + 1) % self.frames.count - - if self.currentFrameIndex < self.frames.count { - self.imageNode.image = self.frames[self.currentFrameIndex] - } - } - } -} +import BatchVideoRendering +import GifVideoLayer private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white @@ -142,14 +44,10 @@ private final class VisualMediaItemInteraction { private final class VisualMediaItemNode: ASDisplayNode { private let context: AccountContext + private let videoContext: BatchVideoRenderingContext private let interaction: VisualMediaItemInteraction - private var videoLayerFrameManager: SoftwareVideoLayerFrameManager? - private var sampleBufferLayer: SampleBufferLayer? - private var displayLink: ConstantDisplayLinkAnimator? - private var displayLinkTimestamp: Double = 0.0 - - private var frameSequenceThumbnailNode: FrameSequenceThumbnailNode? + private var gifVideoLayer: GifVideoLayer? private let containerNode: ContextControllerSourceNode private let imageNode: TransformImageNode @@ -166,9 +64,10 @@ private final class VisualMediaItemNode: ASDisplayNode { private var hasVisibility: Bool = false - init(context: AccountContext, interaction: VisualMediaItemInteraction) { + init(context: AccountContext, interaction: VisualMediaItemInteraction, videoContext: BatchVideoRenderingContext) { self.context = context self.interaction = interaction + self.videoContext = videoContext self.containerNode = ContextControllerSourceNode() self.imageNode = TransformImageNode() @@ -295,25 +194,16 @@ private final class VisualMediaItemNode: ASDisplayNode { } if let file = media as? TelegramMediaFile, file.isAnimated, self.context.sharedContext.energyUsageSettings.autoplayGif { - if self.videoLayerFrameManager == nil { - let sampleBufferLayer: SampleBufferLayer - if let current = self.sampleBufferLayer { - sampleBufferLayer = current - } else { - sampleBufferLayer = takeSampleBufferLayer() - self.sampleBufferLayer = sampleBufferLayer - self.imageNode.layer.addSublayer(sampleBufferLayer.layer) - } - - self.videoLayerFrameManager = SoftwareVideoLayerFrameManager(account: self.context.account, userLocation: .peer(item.message.id.peerId), userContentType: .other, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), layerHolder: sampleBufferLayer) - self.videoLayerFrameManager?.start() + if self.gifVideoLayer == nil { + let gifVideoLayer = GifVideoLayer(context: self.context, batchVideoContext: self.videoContext, userLocation: .peer(item.message.id.peerId), file: FileMediaReference.message(message: MessageReference(item.message), media: file), synchronousLoad: false) + self.gifVideoLayer = gifVideoLayer + self.imageNode.layer.addSublayer(gifVideoLayer) } } else { - if let sampleBufferLayer = self.sampleBufferLayer { - sampleBufferLayer.layer.removeFromSuperlayer() - self.sampleBufferLayer = nil + if let gifVideoLayer = self.gifVideoLayer { + gifVideoLayer.removeFromSuperlayer() + self.gifVideoLayer = nil } - self.videoLayerFrameManager = nil } if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) { @@ -427,8 +317,8 @@ private final class VisualMediaItemNode: ASDisplayNode { self.containerNode.frame = imageFrame self.imageNode.frame = imageFrame - if let sampleBufferLayer = self.sampleBufferLayer { - sampleBufferLayer.layer.frame = imageFrame + if let gifVideoLayer = self.gifVideoLayer { + gifVideoLayer.frame = imageFrame } if let mediaDimensions = mediaDimensions { @@ -442,54 +332,9 @@ private final class VisualMediaItemNode: ASDisplayNode { func updateIsVisible(_ isVisible: Bool) { self.hasVisibility = isVisible - if let _ = self.videoLayerFrameManager { - let displayLink: ConstantDisplayLinkAnimator - if let current = self.displayLink { - displayLink = current - } else { - displayLink = ConstantDisplayLinkAnimator { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.videoLayerFrameManager?.tick(timestamp: strongSelf.displayLinkTimestamp) - strongSelf.displayLinkTimestamp += 1.0 / 30.0 - } - displayLink.frameInterval = 2 - self.displayLink = displayLink - } + if let gifVideoLayer = self.gifVideoLayer { + gifVideoLayer.shouldBeAnimating = self.hasVisibility && !self.isHidden } - self.displayLink?.isPaused = !self.hasVisibility || self.isHidden - - /*if isVisible { - if let item = self.item?.0, let file = self.item?.1 as? TelegramMediaFile, !file.isAnimated { - if self.frameSequenceThumbnailNode == nil { - let frameSequenceThumbnailNode = FrameSequenceThumbnailNode(context: context, file: .message(message: MessageReference(item.message), media: file)) - self.frameSequenceThumbnailNode = frameSequenceThumbnailNode - self.imageNode.addSubnode(frameSequenceThumbnailNode) - } - if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode { - let size = self.bounds.size - frameSequenceThumbnailNode.frame = CGRect(origin: CGPoint(), size: size) - frameSequenceThumbnailNode.updateLayout(size: size) - } - } else { - if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode { - self.frameSequenceThumbnailNode = nil - frameSequenceThumbnailNode.removeFromSupernode() - } - } - } else { - if let frameSequenceThumbnailNode = self.frameSequenceThumbnailNode { - self.frameSequenceThumbnailNode = nil - frameSequenceThumbnailNode.removeFromSupernode() - } - }*/ - - self.frameSequenceThumbnailNode?.updateIsPlaying(isVisible) - } - - func tick() { - self.frameSequenceThumbnailNode?.tick() } func updateSelectionState(animated: Bool) { @@ -566,7 +411,7 @@ private final class VisualMediaItemNode: ASDisplayNode { } else { self.isHidden = false } - self.displayLink?.isPaused = !self.hasVisibility || self.isHidden + self.gifVideoLayer?.shouldBeAnimating = self.hasVisibility && !self.isHidden } } @@ -774,6 +619,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe private let chatControllerInteraction: ChatControllerInteraction private let contentType: ContentType + private let videoContext: BatchVideoRenderingContext + weak var parentController: ViewController? private let scrollNode: ASScrollNode @@ -806,8 +653,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe private var isFirstHistoryView: Bool = true private var decelerationAnimator: ConstantDisplayLinkAnimator? - - private var animationTimer: SwiftSignalKit.Timer? private let statusPromise = Promise(nil) var status: Signal { @@ -831,6 +676,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe self.floatingHeaderNode = FloatingHeaderNode() self.floatingHeaderNode.alpha = 0.0 + self.videoContext = BatchVideoRenderingContext(context: self.context) + super.init() self._itemInteraction = VisualMediaItemInteraction( @@ -875,17 +722,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe itemNode.updateHiddenMedia() } }) - - let animationTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: true, completion: { [weak self] in - guard let strongSelf = self else { - return - } - for (_, itemNode) in strongSelf.visibleMediaItems { - itemNode.tick() - } - }, queue: .mainQueue()) - self.animationTimer = animationTimer - animationTimer.start() self.statusPromise.set(context.engine.data.subscribe( TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: chatLocation.threadId, tag: tagMaskForType(self.contentType)) @@ -910,7 +746,6 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe deinit { self.listDisposable.dispose() self.hiddenMediaDisposable?.dispose() - self.animationTimer?.invalidate() } func ensureMessageIsVisible(id: MessageId) { @@ -1164,7 +999,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe if let current = self.visibleMediaItems[stableId] { itemNode = current } else { - itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction) + itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction, videoContext: self.videoContext) self.visibleMediaItems[stableId] = itemNode self.scrollNode.addSubnode(itemNode) } diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 463453523c..7258a66e95 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -675,7 +675,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] case .generic: createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod) case .supergroup: - createSignal = context.engine.peers.createSupergroup(title: title, description: nil) + createSignal = context.engine.peers.createSupergroup(title: title, description: nil, ttlPeriod: ttlPeriod) |> map { peerId -> CreateGroupResult? in return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: [])) } @@ -730,7 +730,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } let createGroupSignal: (Bool) -> Signal = { isForum in - return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum) + return context.engine.peers.createSupergroup(title: title, description: nil, isForum: isForum, ttlPeriod: ttlPeriod) |> map { peerId -> CreateGroupResult? in return CreateGroupResult(peerId: peerId, result: TelegramInvitePeersResult(forbiddenPeers: [])) } @@ -767,7 +767,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } else if isForum || group.userAdminRights != nil { createSignal = createGroupSignal(isForum) } else { - createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: nil) + createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod) } if group.userAdminRights?.rights.contains(.canBeAnonymous) == true {