This commit is contained in:
Ali 2023-06-19 23:59:38 +03:00
parent 45a39e4d47
commit 39b395c2fa
24 changed files with 975 additions and 379 deletions

View File

@ -867,6 +867,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStorageManagementController(context: AccountContext) -> ViewController func makeStorageManagementController(context: AccountContext) -> ViewController
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController
func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToChatController(_ params: NavigateToChatControllerParams)
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>

View File

@ -218,6 +218,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource)
self.accessoryPanelContainer = ASDisplayNode()
self.tabBarItemContextActionType = .always self.tabBarItemContextActionType = .always
self.automaticallyControlPresentationContextLayout = false self.automaticallyControlPresentationContextLayout = false
@ -2275,7 +2277,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var primaryContent: ChatListHeaderComponent.Content? var primaryContent: ChatListHeaderComponent.Content?
if let primaryContext = self.primaryContext { if let primaryContext = self.primaryContext {
var backTitle: String? var backTitle: String?
if let previousItem = self.navigationBar?.previousItem { if let previousItem = self.previousItem {
switch previousItem { switch previousItem {
case let .item(item): case let .item(item):
backTitle = item.title ?? self.presentationData.strings.Common_Back backTitle = item.title ?? self.presentationData.strings.Common_Back

View File

@ -1884,6 +1884,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
uploadProgress: self.controller?.storyUploadProgress, uploadProgress: self.controller?.storyUploadProgress,
tabsNode: tabsNode, tabsNode: tabsNode,
tabsNodeIsSearch: tabsNodeIsSearch, tabsNodeIsSearch: tabsNodeIsSearch,
accessoryPanelContainer: self.controller?.accessoryPanelContainer,
accessoryPanelContainerHeight: self.controller?.accessoryPanelContainerHeight ?? 0.0,
activateSearch: { [weak self] searchContentNode in activateSearch: { [weak self] searchContentNode in
guard let self, let controller = self.controller else { guard let self, let controller = self.controller else {
return return

View File

@ -373,6 +373,8 @@ final class ContactsControllerNode: ASDisplayNode {
uploadProgress: nil, uploadProgress: nil,
tabsNode: tabsNode, tabsNode: tabsNode,
tabsNodeIsSearch: tabsNodeIsSearch, tabsNodeIsSearch: tabsNodeIsSearch,
accessoryPanelContainer: nil,
accessoryPanelContainerHeight: 0.0,
activateSearch: { [weak self] searchContentNode in activateSearch: { [weak self] searchContentNode in
guard let self else { guard let self else {
return return

View File

@ -325,11 +325,14 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
if i == 0 { if i == 0 {
if canBeClosed { if canBeClosed {
controllers[i].transitionNavigationBar?.previousItem = .close controllers[i].transitionNavigationBar?.previousItem = .close
controllers[i].previousItem = .close
} else { } else {
controllers[i].transitionNavigationBar?.previousItem = nil controllers[i].transitionNavigationBar?.previousItem = nil
controllers[i].previousItem = nil
} }
} else { } else {
controllers[i].transitionNavigationBar?.previousItem = .item(controllers[i - 1].navigationItem) controllers[i].transitionNavigationBar?.previousItem = .item(controllers[i - 1].navigationItem)
controllers[i].previousItem = .item(controllers[i - 1].navigationItem)
} }
} }

View File

@ -155,6 +155,8 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
return self.prefersOnScreenNavigationHidden return self.prefersOnScreenNavigationHidden
} }
public internal(set) var previousItem: NavigationPreviousAction?
open var navigationPresentation: ViewControllerNavigationPresentation = .default open var navigationPresentation: ViewControllerNavigationPresentation = .default
open var _presentedInModal: Bool = false open var _presentedInModal: Bool = false

View File

@ -61,6 +61,9 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
open class TelegramBaseController: ViewController, KeyShortcutResponder { open class TelegramBaseController: ViewController, KeyShortcutResponder {
private let context: AccountContext private let context: AccountContext
public var accessoryPanelContainer: ASDisplayNode?
public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
public let locationBroadcastPanelSource: LocationBroadcastPanelSource public let locationBroadcastPanelSource: LocationBroadcastPanelSource
public let groupCallPanelSource: GroupCallPanelSource public let groupCallPanelSource: GroupCallPanelSource
@ -76,7 +79,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?
public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem?
public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? public private(set) var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode? private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode?
private var locationBroadcastPeers: [EnginePeer]? private var locationBroadcastPeers: [EnginePeer]?
@ -84,7 +87,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel? private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
private var groupCallPanelData: GroupCallPanelData? private var groupCallPanelData: GroupCallPanelData?
private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel? public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
private var dismissingPanel: ASDisplayNode? private var dismissingPanel: ASDisplayNode?
@ -96,14 +99,16 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
override open var additionalNavigationBarHeight: CGFloat { override open var additionalNavigationBarHeight: CGFloat {
var height: CGFloat = 0.0 var height: CGFloat = 0.0
if let _ = self.groupCallAccessoryPanel { if self.accessoryPanelContainer == nil {
height += 50.0 if let _ = self.groupCallAccessoryPanel {
} height += 50.0
if let _ = self.mediaAccessoryPanel { }
height += MediaNavigationAccessoryHeaderNode.minimizedHeight if let _ = self.mediaAccessoryPanel {
} height += MediaNavigationAccessoryHeaderNode.minimizedHeight
if let _ = self.locationBroadcastAccessoryPanel { }
height += MediaNavigationAccessoryHeaderNode.minimizedHeight if let _ = self.locationBroadcastAccessoryPanel {
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
}
} }
return height return height
} }
@ -401,16 +406,38 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight
// if !self.displayNavigationBar {
// navigationHeight = 0.0 let mediaAccessoryPanelHidden: Bool
// } switch self.mediaAccessoryPanelVisibility {
case .always:
mediaAccessoryPanelHidden = false
case .none:
mediaAccessoryPanelHidden = true
case let .specific(size):
mediaAccessoryPanelHidden = size != layout.metrics.widthClass
}
var additionalHeight: CGFloat = 0.0 var additionalHeight: CGFloat = 0.0
var panelStartY: CGFloat = 0.0
if self.accessoryPanelContainer == nil {
var negativeHeight: CGFloat = 0.0
if let _ = self.groupCallPanelData {
negativeHeight += 50.0
}
if let _ = self.locationBroadcastPeers, let _ = self.locationBroadcastMode {
negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight
}
if let _ = self.playlistStateAndType, !mediaAccessoryPanelHidden {
negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight
}
panelStartY = navigationHeight.isZero ? (-negativeHeight) : (navigationHeight + additionalHeight + UIScreenPixel)
}
if let groupCallPanelData = self.groupCallPanelData { if let groupCallPanelData = self.groupCallPanelData {
let panelHeight: CGFloat = 50.0 let panelHeight: CGFloat = 50.0
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
additionalHeight += panelHeight additionalHeight += panelHeight
panelStartY += panelHeight
let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel
if let current = self.groupCallAccessoryPanel { if let current = self.groupCallAccessoryPanel {
@ -429,7 +456,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream) activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream)
) )
}) })
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel) if let accessoryPanelContainer = self.accessoryPanelContainer {
accessoryPanelContainer.addSubnode(groupCallAccessoryPanel)
} else {
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel)
}
self.groupCallAccessoryPanel = groupCallAccessoryPanel self.groupCallAccessoryPanel = groupCallAccessoryPanel
groupCallAccessoryPanel.frame = panelFrame groupCallAccessoryPanel.frame = panelFrame
@ -452,8 +483,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode { if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
additionalHeight += panelHeight additionalHeight += panelHeight
panelStartY += panelHeight
let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel
if let current = self.locationBroadcastAccessoryPanel { if let current = self.locationBroadcastAccessoryPanel {
@ -576,7 +608,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
}) })
self.navigationBar?.additionalContentNode.addSubnode(locationBroadcastAccessoryPanel) if let accessoryPanelContainer = self.accessoryPanelContainer {
accessoryPanelContainer.addSubnode(locationBroadcastAccessoryPanel)
} else {
self.navigationBar?.additionalContentNode.addSubnode(locationBroadcastAccessoryPanel)
}
self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel
locationBroadcastAccessoryPanel.frame = panelFrame locationBroadcastAccessoryPanel.frame = panelFrame
@ -607,19 +643,12 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
} }
} }
let mediaAccessoryPanelHidden: Bool
switch self.mediaAccessoryPanelVisibility {
case .always:
mediaAccessoryPanelHidden = false
case .none:
mediaAccessoryPanelHidden = true
case let .specific(size):
mediaAccessoryPanelHidden = size != layout.metrics.widthClass
}
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden { if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden {
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight)), size: CGSize(width: layout.size.width, height: panelHeight)) let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
additionalHeight += panelHeight
panelStartY += panelHeight
if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type {
transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame)
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition)
@ -848,9 +877,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
} }
mediaAccessoryPanel.frame = panelFrame mediaAccessoryPanel.frame = panelFrame
if let dismissingPanel = self.dismissingPanel { if let dismissingPanel = self.dismissingPanel {
self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) if let accessoryPanelContainer = self.accessoryPanelContainer {
accessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel)
} else {
self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel)
}
} else { } else {
self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel) if let accessoryPanelContainer = self.accessoryPanelContainer {
accessoryPanelContainer.addSubnode(mediaAccessoryPanel)
} else {
self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel)
}
} }
self.mediaAccessoryPanel = (mediaAccessoryPanel, type) self.mediaAccessoryPanel = (mediaAccessoryPanel, type)
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate)
@ -889,6 +926,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
} }
self.accessoryPanelContainerHeight = additionalHeight
} }
open var keyShortcuts: [KeyShortcut] { open var keyShortcuts: [KeyShortcut] {

View File

@ -25,6 +25,8 @@ public final class ChatListNavigationBar: Component {
public let uploadProgress: Float? public let uploadProgress: Float?
public let tabsNode: ASDisplayNode? public let tabsNode: ASDisplayNode?
public let tabsNodeIsSearch: Bool public let tabsNodeIsSearch: Bool
public let accessoryPanelContainer: ASDisplayNode?
public let accessoryPanelContainerHeight: CGFloat
public let activateSearch: (NavigationBarSearchContentNode) -> Void public let activateSearch: (NavigationBarSearchContentNode) -> Void
public let openStatusSetup: (UIView) -> Void public let openStatusSetup: (UIView) -> Void
@ -44,6 +46,8 @@ public final class ChatListNavigationBar: Component {
uploadProgress: Float?, uploadProgress: Float?,
tabsNode: ASDisplayNode?, tabsNode: ASDisplayNode?,
tabsNodeIsSearch: Bool, tabsNodeIsSearch: Bool,
accessoryPanelContainer: ASDisplayNode?,
accessoryPanelContainerHeight: CGFloat,
activateSearch: @escaping (NavigationBarSearchContentNode) -> Void, activateSearch: @escaping (NavigationBarSearchContentNode) -> Void,
openStatusSetup: @escaping (UIView) -> Void openStatusSetup: @escaping (UIView) -> Void
) { ) {
@ -62,6 +66,8 @@ public final class ChatListNavigationBar: Component {
self.uploadProgress = uploadProgress self.uploadProgress = uploadProgress
self.tabsNode = tabsNode self.tabsNode = tabsNode
self.tabsNodeIsSearch = tabsNodeIsSearch self.tabsNodeIsSearch = tabsNodeIsSearch
self.accessoryPanelContainer = accessoryPanelContainer
self.accessoryPanelContainerHeight = accessoryPanelContainerHeight
self.activateSearch = activateSearch self.activateSearch = activateSearch
self.openStatusSetup = openStatusSetup self.openStatusSetup = openStatusSetup
} }
@ -112,6 +118,12 @@ public final class ChatListNavigationBar: Component {
if lhs.tabsNodeIsSearch != rhs.tabsNodeIsSearch { if lhs.tabsNodeIsSearch != rhs.tabsNodeIsSearch {
return false return false
} }
if lhs.accessoryPanelContainer !== rhs.accessoryPanelContainer {
return false
}
if lhs.accessoryPanelContainerHeight != rhs.accessoryPanelContainerHeight {
return false
}
return true return true
} }
@ -199,7 +211,7 @@ public final class ChatListNavigationBar: Component {
} }
public func applyScroll(offset: CGFloat, allowAvatarsExpansion: Bool, forceUpdate: Bool = false, transition: Transition) { public func applyScroll(offset: CGFloat, allowAvatarsExpansion: Bool, forceUpdate: Bool = false, transition: Transition) {
if self.currentAllowAvatarsExpansion != allowAvatarsExpansion, allowAvatarsExpansion { if self.currentAllowAvatarsExpansion != allowAvatarsExpansion, allowAvatarsExpansion, !transition.animation.isImmediate {
self.addStoriesUnlockedAnimation(duration: 0.3, animateScrollUnlocked: false) self.addStoriesUnlockedAnimation(duration: 0.3, animateScrollUnlocked: false)
} }
@ -317,6 +329,9 @@ public final class ChatListNavigationBar: Component {
if component.tabsNode != nil { if component.tabsNode != nil {
searchFrame.origin.y -= 40.0 searchFrame.origin.y -= 40.0
} }
if !component.isSearchActive {
searchFrame.origin.y -= component.accessoryPanelContainerHeight
}
let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance)) let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance))
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
@ -407,10 +422,15 @@ public final class ChatListNavigationBar: Component {
} }
var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0)) var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0))
if !component.isSearchActive {
tabsFrame.origin.y -= component.accessoryPanelContainerHeight
}
if component.tabsNode != nil { if component.tabsNode != nil {
tabsFrame.origin.y -= 46.0 tabsFrame.origin.y -= 46.0
} }
let accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - component.accessoryPanelContainerHeight), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight))
if let disappearingTabsView = self.disappearingTabsView { if let disappearingTabsView = self.disappearingTabsView {
disappearingTabsView.layer.anchorPoint = CGPoint() disappearingTabsView.layer.anchorPoint = CGPoint()
transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? (-currentLayout.size.height + 2.0) : 0.0)) transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? (-currentLayout.size.height + 2.0) : 0.0))
@ -441,6 +461,23 @@ public final class ChatListNavigationBar: Component {
tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame.offsetBy(dx: 0.0, dy: component.tabsNodeIsSearch ? (-currentLayout.size.height + 2.0) : 0.0)) tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame.offsetBy(dx: 0.0, dy: component.tabsNodeIsSearch ? (-currentLayout.size.height + 2.0) : 0.0))
} }
if let accessoryPanelContainer = component.accessoryPanelContainer {
var tabsNodeTransition = transition
if accessoryPanelContainer.view.superview !== self {
accessoryPanelContainer.view.layer.anchorPoint = CGPoint()
tabsNodeTransition = .immediate
accessoryPanelContainer.view.alpha = 1.0
self.addSubview(accessoryPanelContainer.view)
if !transition.animation.isImmediate {
accessoryPanelContainer.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
transition.setAlpha(view: accessoryPanelContainer.view, alpha: 1.0)
}
tabsNodeTransition.setFrameWithAdditivePosition(view: accessoryPanelContainer.view, frame: accessoryPanelContainerFrame)
}
} }
public func updateStoryUploadProgress(storyUploadProgress: Float?) { public func updateStoryUploadProgress(storyUploadProgress: Float?) {
@ -464,6 +501,8 @@ public final class ChatListNavigationBar: Component {
uploadProgress: storyUploadProgress, uploadProgress: storyUploadProgress,
tabsNode: component.tabsNode, tabsNode: component.tabsNode,
tabsNodeIsSearch: component.tabsNodeIsSearch, tabsNodeIsSearch: component.tabsNodeIsSearch,
accessoryPanelContainer: component.accessoryPanelContainer,
accessoryPanelContainerHeight: component.accessoryPanelContainerHeight,
activateSearch: component.activateSearch, activateSearch: component.activateSearch,
openStatusSetup: component.openStatusSetup openStatusSetup: component.openStatusSetup
) )
@ -547,6 +586,10 @@ public final class ChatListNavigationBar: Component {
contentHeight += 40.0 contentHeight += 40.0
} }
if component.accessoryPanelContainer != nil && !component.isSearchActive {
contentHeight += component.accessoryPanelContainerHeight
}
let size = CGSize(width: availableSize.width, height: contentHeight) let size = CGSize(width: availableSize.width, height: contentHeight)
self.currentLayout = CurrentLayout(size: size) self.currentLayout = CurrentLayout(size: size)

View File

@ -16,7 +16,7 @@ private extension MessageInputActionButtonComponent.Mode {
case .attach: case .attach:
return "Chat/Input/Text/IconAttachment" return "Chat/Input/Text/IconAttachment"
case .forward: case .forward:
return "Chat/Input/Text/IconForward" return "Chat/Input/Text/IconForwardSend"
default: default:
return nil return nil
} }

View File

@ -889,6 +889,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private weak var pendingOpenListContext: PeerStoryListContentContextImpl?
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) {
self.context = context self.context = context
@ -952,6 +954,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
} }
if self.pendingOpenListContext != nil {
return
}
//TODO:selection //TODO:selection
let listContext = PeerStoryListContentContextImpl( let listContext = PeerStoryListContentContextImpl(
context: self.context, context: self.context,
@ -959,6 +965,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
listContext: self.listSource, listContext: self.listSource,
initialId: item.story.id initialId: item.story.id
) )
self.pendingOpenListContext = listContext
self.itemGrid.isUserInteractionEnabled = false
let _ = (listContext.state let _ = (listContext.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in |> deliverOnMainQueue).start(next: { [weak self] _ in
@ -966,6 +975,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
return return
} }
guard let pendingOpenListContext = self.pendingOpenListContext, pendingOpenListContext === listContext else {
return
}
self.pendingOpenListContext = nil
self.itemGrid.isUserInteractionEnabled = true
var transitionIn: StoryContainerScreen.TransitionIn? var transitionIn: StoryContainerScreen.TransitionIn?
let story = item.story let story = item.story
@ -1016,6 +1031,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}, },
updateView: { view, state, transition in updateView: { view, state, transition in
(view as? ItemTransitionView)?.update(state: state, transition: transition) (view as? ItemTransitionView)?.update(state: state, transition: transition)
},
insertCloneTransitionView: { [weak self] view in
guard let self else {
return
}
self.view.insertSubview(view, aboveSubview: self.itemGrid.view)
} }
), ),
destinationRect: self.itemGrid.view.convert(itemRect, to: self.view), destinationRect: self.itemGrid.view.convert(itemRect, to: self.view),

View File

@ -56,6 +56,11 @@ swift_library(
"//submodules/TelegramStringFormatting", "//submodules/TelegramStringFormatting",
"//submodules/ShimmerEffect", "//submodules/ShimmerEffect",
"//submodules/ImageCompression", "//submodules/ImageCompression",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/InvisibleInkDustNode",
"//submodules/PresentationDataUtils",
"//submodules/UrlEscaping",
"//submodules/OverlayStatusController",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -851,7 +851,7 @@ private final class StoryContainerScreenComponent: Component {
} }
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - itemSetContainerSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - itemSetContainerSize.height) / 2.0)), size: itemSetContainerSize) let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - itemSetContainerSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - itemSetContainerSize.height) / 2.0)), size: itemSetContainerSize)
if let itemSetComponentView = itemSetView.view.view { if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
if itemSetView.superview == nil { if itemSetView.superview == nil {
self.addSubview(itemSetView) self.addSubview(itemSetView)
} }
@ -866,6 +866,10 @@ private final class StoryContainerScreenComponent: Component {
itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
itemSetTransition.setSublayerTransform(view: itemSetView, transform: CATransform3DMakeScale(dismissPanScale, dismissPanScale, 1.0)) itemSetTransition.setSublayerTransform(view: itemSetView, transform: CATransform3DMakeScale(dismissPanScale, dismissPanScale, 1.0))
itemSetTransition.setPosition(view: itemSetComponentView.transitionCloneContainerView, position: itemFrame.center.offsetBy(dx: 0.0, dy: dismissPanOffset))
itemSetTransition.setBounds(view: itemSetComponentView.transitionCloneContainerView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
itemSetTransition.setSublayerTransform(view: itemSetComponentView.transitionCloneContainerView, transform: CATransform3DMakeScale(dismissPanScale, dismissPanScale, 1.0))
itemSetTransition.setPosition(view: itemSetComponentView, position: CGRect(origin: CGPoint(), size: itemFrame.size).center) itemSetTransition.setPosition(view: itemSetComponentView, position: CGRect(origin: CGPoint(), size: itemFrame.size).center)
itemSetTransition.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) itemSetTransition.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
@ -1037,13 +1041,16 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
public final class TransitionView { public final class TransitionView {
public let makeView: () -> UIView public let makeView: () -> UIView
public let updateView: (UIView, TransitionState, Transition) -> Void public let updateView: (UIView, TransitionState, Transition) -> Void
public let insertCloneTransitionView: ((UIView) -> Void)?
public init( public init(
makeView: @escaping () -> UIView, makeView: @escaping () -> UIView,
updateView: @escaping (UIView, TransitionState, Transition) -> Void updateView: @escaping (UIView, TransitionState, Transition) -> Void,
insertCloneTransitionView: ((UIView) -> Void)?
) { ) {
self.makeView = makeView self.makeView = makeView
self.updateView = updateView self.updateView = updateView
self.insertCloneTransitionView = insertCloneTransitionView
} }
} }
@ -1092,6 +1099,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
} }
private let context: AccountContext private let context: AccountContext
private var didAnimateIn: Bool = false
private var isDismissed: Bool = false private var isDismissed: Bool = false
private let focusedItemPromise = Promise<StoryId?>(nil) private let focusedItemPromise = Promise<StoryId?>(nil)
@ -1141,8 +1149,12 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
self.view.disablesInteractiveModalDismiss = true self.view.disablesInteractiveModalDismiss = true
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { if !self.didAnimateIn {
componentView.animateIn() self.didAnimateIn = true
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
componentView.animateIn()
}
} }
} }

View File

@ -3,8 +3,23 @@ import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import MultilineTextComponent import MultilineTextComponent
import AccountContext
import TelegramCore
import TextNodeWithEntities
import TextFormat
import InvisibleInkDustNode
import UrlEscaping
final class StoryContentCaptionComponent: Component { final class StoryContentCaptionComponent: Component {
enum Action {
case url(url: String, concealed: Bool)
case textMention(String)
case peerMention(peerId: EnginePeer.Id, mention: String)
case hashtag(String?, String)
case bankCard(String)
case customEmoji(TelegramMediaFile)
}
final class ExternalState { final class ExternalState {
fileprivate(set) var isExpanded: Bool = false fileprivate(set) var isExpanded: Bool = false
@ -25,20 +40,38 @@ final class StoryContentCaptionComponent: Component {
} }
let externalState: ExternalState let externalState: ExternalState
let context: AccountContext
let text: String let text: String
let entities: [MessageTextEntity]
let action: (Action) -> Void
init( init(
externalState: ExternalState, externalState: ExternalState,
text: String context: AccountContext,
text: String,
entities: [MessageTextEntity],
action: @escaping (Action) -> Void
) { ) {
self.externalState = externalState self.externalState = externalState
self.context = context
self.text = text self.text = text
self.entities = entities
self.action = action
} }
static func ==(lhs: StoryContentCaptionComponent, rhs: StoryContentCaptionComponent) -> Bool { static func ==(lhs: StoryContentCaptionComponent, rhs: StoryContentCaptionComponent) -> Bool {
if lhs.externalState !== rhs.externalState {
return false
}
if lhs.context !== rhs.context {
return false
}
if lhs.text != rhs.text { if lhs.text != rhs.text {
return false return false
} }
if lhs.entities != rhs.entities {
return false
}
return true return true
} }
@ -70,7 +103,9 @@ final class StoryContentCaptionComponent: Component {
private let shadowGradientLayer: SimpleGradientLayer private let shadowGradientLayer: SimpleGradientLayer
private let shadowPlainLayer: SimpleLayer private let shadowPlainLayer: SimpleLayer
private let text = ComponentView<Empty>() private var textNode: TextNodeWithEntities?
private var linkHighlightingNode: LinkHighlightingNode?
private var dustNode: InvisibleInkDustNode?
private var component: StoryContentCaptionComponent? private var component: StoryContentCaptionComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -133,10 +168,10 @@ final class StoryContentCaptionComponent: Component {
if !self.bounds.contains(point) { if !self.bounds.contains(point) {
return nil return nil
} }
if let textView = self.text.view { if let textView = self.textNode?.textNode.view {
let textLocalPoint = self.convert(point, to: textView) let textLocalPoint = self.convert(point, to: textView)
if textLocalPoint.y >= -7.0 { if textLocalPoint.y >= -7.0 {
return self.scrollView return textView
} }
} }
@ -205,6 +240,103 @@ final class StoryContentCaptionComponent: Component {
} }
} }
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let component = self.component, let textNode = self.textNode {
switch gesture {
case .tap:
let titleFrame = textNode.textNode.view.bounds
if titleFrame.contains(location) {
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
return
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true
if let (attributeText, fullText) = textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
}
component.action(.url(url: url, concealed: concealed))
return
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
component.action(.peerMention(peerId: peerMention.peerId, mention: peerMention.mention))
return
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
component.action(.textMention(peerName))
return
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
component.action(.hashtag(hashtag.peerName, hashtag.hashtag))
return
} else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String {
component.action(.bankCard(bankCard))
return
} else if let emoji = attributes[NSAttributedString.Key(rawValue: ChatTextInputAttributes.customEmoji.rawValue)] as? ChatTextInputTextCustomEmojiAttribute, let file = emoji.file {
component.action(.customEmoji(file))
return
}
}
}
self.expand(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
default:
break
}
}
default:
break
}
}
private func updateTouchesAtPoint(_ point: CGPoint?) {
guard let textNode = self.textNode else {
return
}
var rects: [CGRect]?
var spoilerRects: [CGRect]?
if let point = point {
let textNodeFrame = textNode.textNode.bounds
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag,
TelegramTextAttributes.Timecode,
TelegramTextAttributes.BankCard
]
for name in possibleNames {
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
rects = textNode.textNode.attributeRects(name: name, at: index)
break
}
}
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] {
spoilerRects = textNode.textNode.attributeRects(name: TelegramTextAttributes.Spoiler, at: index)
}
}
}
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, let dustNode = self.dustNode, !dustNode.isRevealed {
} else if let rects = rects {
let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: UIColor(white: 1.0, alpha: 0.5))
self.linkHighlightingNode = linkHighlightingNode
self.scrollView.insertSubview(linkHighlightingNode.view, belowSubview: textNode.textNode.view)
}
linkHighlightingNode.frame = textNode.textNode.view.frame
linkHighlightingNode.updateRects(rects)
} else if let linkHighlightingNode = self.linkHighlightingNode {
self.linkHighlightingNode = nil
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
linkHighlightingNode?.removeFromSupernode()
})
}
}
func update(component: StoryContentCaptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(component: StoryContentCaptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.ignoreExternalState = true self.ignoreExternalState = true
@ -213,29 +345,64 @@ final class StoryContentCaptionComponent: Component {
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0
let verticalInset: CGFloat = 7.0 let verticalInset: CGFloat = 7.0
let textContainerSize = CGSize(width: availableSize.width - sideInset * 2.0 - 50.0, height: availableSize.height - verticalInset * 2.0) let textContainerSize = CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - verticalInset * 2.0)
let textSize = self.text.update( let attributedText = stringWithAppliedEntities(
transition: .immediate, component.text,
component: AnyComponent(MultilineTextComponent( entities: component.entities,
text: .plain(NSAttributedString(string: component.text, font: Font.regular(16.0), textColor: .white)), baseColor: .white,
maximumNumberOfLines: 0 linkColor: .white,
)), baseFont: Font.regular(16.0),
environment: {}, linkFont: Font.regular(16.0),
containerSize: textContainerSize boldFont: Font.semibold(16.0),
italicFont: Font.italic(16.0),
boldItalicFont: Font.semiboldItalic(16.0),
fixedFont: Font.monospace(16.0),
blockQuoteFont: Font.monospace(16.0),
message: nil
) )
let makeLayout = TextNodeWithEntities.asyncLayout(self.textNode)
let textLayout = makeLayout(TextNodeLayoutArguments(
attributedString: attributedText,
maximumNumberOfLines: 0,
truncationType: .end,
constrainedSize: textContainerSize
))
let maxHeight: CGFloat = 50.0 let maxHeight: CGFloat = 50.0
let visibleTextHeight = min(maxHeight, textSize.height) let visibleTextHeight = min(maxHeight, textLayout.0.size.height)
let textOverflowHeight: CGFloat = textSize.height - visibleTextHeight let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight
let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight) let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
if let textView = self.text.view { let textNode = textLayout.1(TextNodeWithEntities.Arguments(
if textView.superview == nil { context: component.context,
self.scrollView.addSubview(textView) cache: component.context.animationCache,
renderer: component.context.animationRenderer,
placeholderColor: UIColor(white: 0.2, alpha: 1.0), attemptSynchronous: true
))
if self.textNode !== textNode {
self.textNode?.textNode.view.removeFromSuperview()
self.textNode = textNode
if textNode.textNode.view.superview == nil {
self.scrollView.addSubview(textNode.textNode.view)
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { point in
return .waitForSingleTap
}
recognizer.highlight = { [weak self] point in
guard let self else {
return
}
self.updateTouchesAtPoint(point)
}
textNode.textNode.view.addGestureRecognizer(recognizer)
} }
textView.frame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textSize)
} }
textNode.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset), size: textLayout.0.size)
self.itemLayout = ItemLayout( self.itemLayout = ItemLayout(
containerSize: availableSize, containerSize: availableSize,

View File

@ -6,7 +6,6 @@ import AppBundle
import ComponentDisplayAdapters import ComponentDisplayAdapters
import ReactionSelectionNode import ReactionSelectionNode
import EntityKeyboard import EntityKeyboard
import StoryFooterPanelComponent
import MessageInputPanelComponent import MessageInputPanelComponent
import TelegramPresentationData import TelegramPresentationData
import SwiftSignalKit import SwiftSignalKit
@ -22,6 +21,7 @@ import ImageCompression
import ShareWithPeersScreen import ShareWithPeersScreen
import PlainButtonComponent import PlainButtonComponent
import TooltipUI import TooltipUI
import PresentationDataUtils
public final class StoryItemSetContainerComponent: Component { public final class StoryItemSetContainerComponent: Component {
public final class ExternalState { public final class ExternalState {
@ -242,7 +242,6 @@ public final class StoryItemSetContainerComponent: Component {
var captionItem: CaptionItem? var captionItem: CaptionItem?
let inputPanel = ComponentView<Empty>() let inputPanel = ComponentView<Empty>()
let footerPanel = ComponentView<Empty>()
let inputPanelExternalState = MessageInputPanelComponent.ExternalState() let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
var displayViewList: Bool = false var displayViewList: Bool = false
@ -274,6 +273,8 @@ public final class StoryItemSetContainerComponent: Component {
private weak var voiceMessagesRestrictedTooltipController: TooltipController? private weak var voiceMessagesRestrictedTooltipController: TooltipController?
let transitionCloneContainerView: UIView
override init(frame: CGRect) { override init(frame: CGRect) {
self.sendMessageContext = StoryItemSetContainerSendMessage() self.sendMessageContext = StoryItemSetContainerSendMessage()
@ -294,6 +295,8 @@ public final class StoryItemSetContainerComponent: Component {
self.closeButton = HighlightableButton() self.closeButton = HighlightableButton()
self.closeButtonIconView = UIImageView() self.closeButtonIconView = UIImageView()
self.transitionCloneContainerView = UIView()
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delaysContentTouches = false self.scrollView.delaysContentTouches = false
@ -648,8 +651,10 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
if component.slice.peer.id == component.context.account.peerId { if component.slice.peer.id == component.context.account.peerId {
self.displayViewList = true if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) self.displayViewList = true
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
} else { } else {
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
inputPanelView.activateInput() inputPanelView.activateInput()
@ -670,16 +675,6 @@ public final class StoryItemSetContainerComponent: Component {
) )
inputPanelView.layer.animateAlpha(from: 0.0, to: inputPanelView.alpha, duration: 0.28) inputPanelView.layer.animateAlpha(from: 0.0, to: inputPanelView.alpha, duration: 0.28)
} }
if let footerPanelView = self.footerPanel.view {
footerPanelView.layer.animatePosition(
from: CGPoint(x: 0.0, y: self.bounds.height - footerPanelView.frame.minY),
to: CGPoint(),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
additive: true
)
footerPanelView.layer.animateAlpha(from: 0.0, to: footerPanelView.alpha, duration: 0.28)
}
if let viewListView = self.viewList?.view.view { if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition( viewListView.layer.animatePosition(
from: CGPoint(x: 0.0, y: self.bounds.height - self.contentContainerView.frame.maxY), from: CGPoint(x: 0.0, y: self.bounds.height - self.contentContainerView.frame.maxY),
@ -749,9 +744,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
func animateOut(transitionOut: StoryContainerScreen.TransitionOut, completion: @escaping () -> Void) { func animateOut(transitionOut: StoryContainerScreen.TransitionOut, completion: @escaping () -> Void) {
self.closeButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in var cleanups: [() -> Void] = []
completion()
})
if let inputPanelView = self.inputPanel.view { if let inputPanelView = self.inputPanel.view {
inputPanelView.layer.animatePosition( inputPanelView.layer.animatePosition(
@ -764,17 +757,6 @@ public final class StoryItemSetContainerComponent: Component {
) )
inputPanelView.layer.animateAlpha(from: inputPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) inputPanelView.layer.animateAlpha(from: inputPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
} }
if let footerPanelView = self.footerPanel.view {
footerPanelView.layer.animatePosition(
from: CGPoint(),
to: CGPoint(x: 0.0, y: self.bounds.height - footerPanelView.frame.minY),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
removeOnCompletion: false,
additive: true
)
footerPanelView.layer.animateAlpha(from: footerPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
if let viewListView = self.viewList?.view.view { if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition( viewListView.layer.animatePosition(
from: CGPoint(), from: CGPoint(),
@ -811,39 +793,75 @@ public final class StoryItemSetContainerComponent: Component {
if let rightInfoView = self.rightInfoItem?.view.view { if let rightInfoView = self.rightInfoItem?.view.view {
if transitionOut.destinationIsAvatar { if transitionOut.destinationIsAvatar {
let transitionView = transitionOut.transitionView let transitionView = transitionOut.transitionView
let transitionViewImpl = transitionView?.makeView()
if let transitionViewImpl { var transitionViewsImpl: [UIView] = []
self.insertSubview(transitionViewImpl, aboveSubview: self.contentContainerView)
if let transitionViewImpl = transitionView?.makeView() {
transitionViewsImpl.append(transitionViewImpl)
let transitionSourceContainerView = UIView(frame: self.bounds)
transitionSourceContainerView.isUserInteractionEnabled = false
self.insertSubview(transitionSourceContainerView, aboveSubview: self.contentContainerView)
transitionSourceContainerView.addSubview(transitionViewImpl)
if let insertCloneTransitionView = transitionView?.insertCloneTransitionView {
if let transitionCloneViewImpl = transitionView?.makeView() {
transitionViewsImpl.append(transitionCloneViewImpl)
let transitionCloneContainerView = self.transitionCloneContainerView
transitionCloneContainerView.isUserInteractionEnabled = false
insertCloneTransitionView(transitionCloneContainerView)
transitionCloneContainerView.frame = transitionCloneContainerView.convert(self.convert(self.bounds, to: nil), from: nil)
transitionCloneContainerView.addSubview(transitionCloneViewImpl)
transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false)
transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
cleanups.append({ [weak transitionCloneContainerView] in
transitionCloneContainerView?.removeFromSuperview()
})
}
}
let rightInfoSourceFrame = rightInfoView.convert(rightInfoView.bounds, to: self) let rightInfoSourceFrame = rightInfoView.convert(rightInfoView.bounds, to: self)
let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: sourceLocalFrame.center, to: rightInfoSourceFrame.center, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true) let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: sourceLocalFrame.center, to: rightInfoSourceFrame.center, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true)
transitionViewImpl.frame = rightInfoSourceFrame for transitionViewImpl in transitionViewsImpl {
transitionViewImpl.alpha = 0.0 transitionViewImpl.frame = rightInfoSourceFrame
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( transitionViewImpl.alpha = 0.0
sourceSize: rightInfoSourceFrame.size, transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
destinationSize: sourceLocalFrame.size, sourceSize: rightInfoSourceFrame.size,
progress: 0.0 destinationSize: sourceLocalFrame.size,
), .immediate) progress: 0.0
), .immediate)
}
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring)) let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transitionViewImpl.alpha = 1.0 for transitionViewImpl in transitionViewsImpl {
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) transitionViewImpl.alpha = 1.0
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
rightInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) rightInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) for transitionViewImpl in transitionViewsImpl {
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
}
transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1] for transitionViewImpl in transitionViewsImpl {
transitionViewImpl.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: 0.3, keyPath: "position", removeOnCompletion: false, additive: false) transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1]
transitionViewImpl.layer.animateBounds(from: CGRect(origin: CGPoint(), size: rightInfoSourceFrame.size), to: CGRect(origin: CGPoint(), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) transitionViewImpl.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: 0.3, keyPath: "position", removeOnCompletion: false, additive: false)
transitionViewImpl.layer.animateBounds(from: CGRect(origin: CGPoint(), size: rightInfoSourceFrame.size), to: CGRect(origin: CGPoint(), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
sourceSize: rightInfoSourceFrame.size, transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
destinationSize: sourceLocalFrame.size, sourceSize: rightInfoSourceFrame.size,
progress: 1.0 destinationSize: sourceLocalFrame.size,
), transition) progress: 1.0
), transition)
}
} }
let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: innerSourceLocalFrame.center, to: rightInfoView.layer.position, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true) let positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: innerSourceLocalFrame.center, to: rightInfoView.layer.position, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true)
@ -867,30 +885,63 @@ public final class StoryItemSetContainerComponent: Component {
if !transitionOut.destinationIsAvatar { if !transitionOut.destinationIsAvatar {
let transitionView = transitionOut.transitionView let transitionView = transitionOut.transitionView
let transitionViewImpl = transitionView?.makeView()
if let transitionViewImpl { var transitionViewsImpl: [UIView] = []
self.insertSubview(transitionViewImpl, belowSubview: self.contentContainerView)
if let transitionViewImpl = transitionView?.makeView() {
transitionViewsImpl.append(transitionViewImpl)
transitionViewImpl.frame = contentSourceFrame let transitionSourceContainerView = UIView(frame: self.bounds)
transitionViewImpl.alpha = 0.0 transitionSourceContainerView.isUserInteractionEnabled = false
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( self.insertSubview(transitionSourceContainerView, belowSubview: self.contentContainerView)
sourceSize: contentSourceFrame.size,
destinationSize: sourceLocalFrame.size, transitionSourceContainerView.addSubview(transitionViewImpl)
progress: 0.0
), .immediate) if let insertCloneTransitionView = transitionView?.insertCloneTransitionView {
if let transitionCloneViewImpl = transitionView?.makeView() {
transitionViewsImpl.append(transitionCloneViewImpl)
let transitionCloneContainerView = self.transitionCloneContainerView
transitionCloneContainerView.isUserInteractionEnabled = false
insertCloneTransitionView(transitionCloneContainerView)
transitionCloneContainerView.addSubview(transitionCloneViewImpl)
transitionSourceContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, removeOnCompletion: false)
transitionCloneContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
cleanups.append({ [weak transitionCloneContainerView] in
transitionCloneContainerView?.removeFromSuperview()
})
}
}
for transitionViewImpl in transitionViewsImpl {
transitionViewImpl.frame = contentSourceFrame
transitionViewImpl.alpha = 0.0
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
sourceSize: contentSourceFrame.size,
destinationSize: sourceLocalFrame.size,
progress: 0.0
), .immediate)
}
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring)) let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transitionViewImpl.alpha = 1.0 for transitionViewImpl in transitionViewsImpl {
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) transitionViewImpl.alpha = 1.0
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
self.contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) for transitionViewImpl in transitionViewsImpl {
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState( transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
sourceSize: contentSourceFrame.size, transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
destinationSize: sourceLocalFrame.size, sourceSize: contentSourceFrame.size,
progress: 1.0 destinationSize: sourceLocalFrame.size,
), transition) progress: 1.0
), transition)
}
} }
} }
@ -919,6 +970,14 @@ public final class StoryItemSetContainerComponent: Component {
visibleItemView.layer.animateScale(from: 1.0, to: innerScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) visibleItemView.layer.animateScale(from: 1.0, to: innerScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} }
} }
self.closeButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
for cleanup in cleanups {
cleanup()
}
cleanups.removeAll()
completion()
})
} }
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
@ -1112,238 +1171,6 @@ public final class StoryItemSetContainerComponent: Component {
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0) containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
) )
/*let footerPanelSize = self.footerPanel.update(
transition: transition,
component: AnyComponent(StoryFooterPanelComponent(
context: component.context,
storyItem: currentItem?.storyItem,
expandViewStats: { [weak self] in
guard let self else {
return
}
if !self.displayViewList {
self.displayViewList = true
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
},
deleteAction: { [weak self] in
guard let self, let component = self.component else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let self, let component = self.component else {
return
}
component.delete()
/*if let currentSlice = self.currentSlice, let index = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) {
let item = currentSlice.items[index]
if currentSlice.items.count == 1 {
component.navigateToItemSet(.next)
} else {
var nextIndex: Int = index + 1
if nextIndex >= currentSlice.items.count {
nextIndex = currentSlice.items.count - 1
}
self.focusedItemId = currentSlice.items[nextIndex].id
currentSlice.items[nextIndex].markAsSeen?()
self.state?.updated(transition: .immediate)
}
item.delete?()
}*/
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
actionSheet.dismissed = { [weak self] _ in
guard let self else {
return
}
self.actionSheet = nil
self.updateIsProgressPaused()
}
self.actionSheet = actionSheet
self.updateIsProgressPaused()
component.presentController(actionSheet)
},
moreAction: { [weak self] sourceView, gesture in
guard let self, let component = self.component, let controller = component.controller() else {
return
}
var items: [ContextMenuItem] = []
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
let privacyText: String
switch component.slice.item.storyItem.privacy?.base {
case .closeFriends:
if additionalCount != 0 {
privacyText = "Close Friends (+\(additionalCount)"
} else {
privacyText = "Close Friends"
}
case .contacts:
if additionalCount != 0 {
privacyText = "Contacts (+\(additionalCount)"
} else {
privacyText = "Contacts"
}
case .nobody:
if additionalCount != 0 {
if additionalCount == 1 {
privacyText = "\(additionalCount) Person"
} else {
privacyText = "\(additionalCount) People"
}
} else {
privacyText = "Only Me"
}
default:
privacyText = "Everyone"
}
items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue(privacyText), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
self.openItemPrivacySettings()
})))
items.append(.action(ContextMenuActionItem(text: "Edit Story", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
self.openStoryEditing()
})))
items.append(.separator)
component.controller()?.forEachController { c in
if let c = c as? UndoOverlayController {
c.dismiss()
}
return true
}
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from profile" : "Save to profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self, let component = self.component else {
return
}
let _ = component.context.engine.messages.updateStoriesArePinned(ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).start()
if component.slice.item.storyItem.isPinned {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
self.component?.presentController(UndoOverlayController(
presentationData: presentationData,
content: .info(title: nil, text: "Story removed from your profile", timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
))
} else {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
self.component?.presentController(UndoOverlayController(
presentationData: presentationData,
content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
))
}
})))
items.append(.action(ContextMenuActionItem(text: "Save image", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
}, action: { _, a in
a(.default)
})))
if component.slice.item.storyItem.isPublic {
items.append(.action(ContextMenuActionItem(text: "Copy link", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self, let component = self.component else {
return
}
let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)
|> deliverOnMainQueue).start(next: { [weak self] link in
guard let self, let component = self.component else {
return
}
if let link {
UIPasteboard.general.string = link
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
component.presentController(UndoOverlayController(
presentationData: presentationData,
content: .linkCopied(text: "Link copied."),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
))
}
})
})))
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { _, a in
a(.default)
})))
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in
guard let self else {
return
}
self.contextController = nil
self.updateIsProgressPaused()
}
self.contextController = contextController
self.updateIsProgressPaused()
controller.present(contextController, in: .window(.root))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 200.0)
)*/
let bottomContentInsetWithoutInput = bottomContentInset let bottomContentInsetWithoutInput = bottomContentInset
var viewListInset: CGFloat = 0.0 var viewListInset: CGFloat = 0.0
@ -1379,8 +1206,10 @@ public final class StoryItemSetContainerComponent: Component {
let outerExpansionFraction: CGFloat let outerExpansionFraction: CGFloat
if self.displayViewList { if self.displayViewList {
outerExpansionFraction = 1.0 outerExpansionFraction = 1.0
} else { } else if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
outerExpansionFraction = component.verticalPanFraction outerExpansionFraction = component.verticalPanFraction
} else {
outerExpansionFraction = 0.0
} }
viewList.view.parentState = state viewList.view.parentState = state
@ -1762,7 +1591,7 @@ public final class StoryItemSetContainerComponent: Component {
if let view = currentCenterInfoItem.view.view { if let view = currentCenterInfoItem.view.view {
var animateIn = false var animateIn = false
if view.superview == nil { if view.superview == nil {
self.contentContainerView.addSubview(view) self.contentContainerView.insertSubview(view, belowSubview: self.closeButton)
animateIn = true animateIn = true
} }
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize)) transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: centerInfoItemSize))
@ -1857,7 +1686,38 @@ public final class StoryItemSetContainerComponent: Component {
transition: captionItemTransition, transition: captionItemTransition,
component: AnyComponent(StoryContentCaptionComponent( component: AnyComponent(StoryContentCaptionComponent(
externalState: captionItem.externalState, externalState: captionItem.externalState,
text: component.slice.item.storyItem.text context: component.context,
text: component.slice.item.storyItem.text,
entities: component.slice.item.storyItem.entities,
action: { [weak self] action in
guard let self, let component = self.component else {
return
}
switch action {
case let .url(url, concealed):
openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in
guard let self, let component = self.component, let controller = component.controller() else {
return
}
controller.present(c, in: .window(.root))
}, openResolved: { [weak self] resolved in
guard let self else {
return
}
self.sendMessageContext.openResolved(view: self, result: resolved, forceExternal: false, concealed: concealed)
})
case let .textMention(value):
self.sendMessageContext.openPeerMention(view: self, name: value)
case let .peerMention(peerId, _):
self.sendMessageContext.openPeerMention(view: self, peerId: peerId)
case let .hashtag(username, value):
self.sendMessageContext.openHashtag(view: self, hashtag: value, peerName: username)
case let .bankCard(value):
let _ = value
case .customEmoji:
break
}
}
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: contentFrame.height) containerSize: CGSize(width: availableSize.width, height: contentFrame.height)
@ -2109,22 +1969,6 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
/*var footerPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputPanelBottomInset - footerPanelSize.height), size: footerPanelSize)
var footerPanelAlpha: CGFloat = (focusedItem?.isMy == true && !self.displayViewList) ? 1.0 : 0.0
if case .regular = component.metrics.widthClass {
footerPanelAlpha *= component.visibilityFraction
}
if self.displayViewList {
footerPanelFrame.origin.y += footerPanelSize.height
}
if let footerPanelView = self.footerPanel.view {
if footerPanelView.superview == nil {
self.addSubview(footerPanelView)
}
transition.setFrame(view: footerPanelView, frame: footerPanelFrame)
transition.setAlpha(view: footerPanelView, alpha: footerPanelAlpha)
}*/
let bottomGradientHeight = inputPanelSize.height + 32.0 let bottomGradientHeight = inputPanelSize.height + 32.0
transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - component.inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight))) transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - component.inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight)))
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0) //transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)

View File

@ -32,6 +32,8 @@ import TelegramPresentationData
import ShareController import ShareController
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import Postbox import Postbox
import OverlayStatusController
import PresentationDataUtils
final class StoryItemSetContainerSendMessage { final class StoryItemSetContainerSendMessage {
weak var attachmentController: AttachmentController? weak var attachmentController: AttachmentController?
@ -46,6 +48,8 @@ final class StoryItemSetContainerSendMessage {
var videoRecorder = Promise<InstantVideoController?>() var videoRecorder = Promise<InstantVideoController?>()
let controllerNavigationDisposable = MetaDisposable() let controllerNavigationDisposable = MetaDisposable()
let enqueueMediaMessageDisposable = MetaDisposable() let enqueueMediaMessageDisposable = MetaDisposable()
let navigationActionDisposable = MetaDisposable()
let resolvePeerByNameDisposable = MetaDisposable()
private(set) var isMediaRecordingLocked: Bool = false private(set) var isMediaRecordingLocked: Bool = false
var wasRecordingDismissed: Bool = false var wasRecordingDismissed: Bool = false
@ -53,6 +57,8 @@ final class StoryItemSetContainerSendMessage {
deinit { deinit {
self.controllerNavigationDisposable.dispose() self.controllerNavigationDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose() self.enqueueMediaMessageDisposable.dispose()
self.navigationActionDisposable.dispose()
self.resolvePeerByNameDisposable.dispose()
} }
func performSendMessageAction( func performSendMessageAction(
@ -1797,4 +1803,258 @@ final class StoryItemSetContainerSendMessage {
let _ = (legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals) let _ = (legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals)
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
} }
func openResolved(view: StoryItemSetContainerComponent.View, result: ResolvedUrl, forceExternal: Bool = false, concealed: Bool = false) {
guard let component = view.component, let navigationController = component.controller()?.navigationController as? NavigationController else {
return
}
let peerId = component.slice.peer.id
component.context.sharedContext.openResolvedUrl(result, context: component.context, urlContext: .chat(peerId: peerId, updatedPresentationData: nil), navigationController: navigationController, forceExternal: forceExternal, openPeer: { [weak self, weak view] peerId, navigation in
guard let self, let view, let component = view.component, let controller = component.controller() as? StoryContainerScreen else {
return
}
controller.dismissWithoutTransitionOut()
switch navigation {
case let .chat(_, subject, peekData):
if let navigationController = controller.navigationController as? NavigationController {
if case let .channel(channel) = peerId, channel.flags.contains(.isForum) {
component.context.sharedContext.navigateToForumChannel(context: component.context, peerId: peerId.id, navigationController: navigationController)
} else {
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
guard let controller, let navigationController else {
return
}
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === controller }) {
viewControllers.insert(chatController, at: index)
} else {
viewControllers.append(chatController)
}
navigationController.setViewControllers(viewControllers, animated: animated)
}))
}
}
case .info:
self.navigationActionDisposable.set((component.context.account.postbox.loadedPeerWithId(peerId.id)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak view] peer in
guard let view, let component = view.component else {
return
}
if peer.restrictionText(platform: "ios", contentSettings: component.context.currentContentSettings.with { $0 }) == nil {
if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
component.controller()?.push(infoController)
}
}
}))
case let .withBotStartPayload(startPayload):
if let navigationController = controller.navigationController as? NavigationController {
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), botStart: startPayload, keepStack: .always))
}
case let .withAttachBot(attachBotStart):
if let navigationController = controller.navigationController as? NavigationController {
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart))
}
default:
break
}
},
sendFile: nil,
sendSticker: nil,
requestMessageActionUrlAuth: nil,
joinVoiceChat: nil,
present: { [weak view] c, a in
guard let view, let component = view.component, let controller = component.controller() else {
return
}
controller.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak view] in
guard let view else {
return
}
view.endEditing(true)
},
contentContext: nil
)
}
func navigateToMessage(view: StoryItemSetContainerComponent.View, messageId: EngineMessage.Id, completion: (() -> Void)?) {
guard let component = view.component else {
return
}
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|> deliverOnMainQueue).start(next: { [weak view] peer in
guard let view, let component = view.component, let controller = component.controller(), let peer = peer else {
return
}
if let navigationController = controller.navigationController as? NavigationController {
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
}
completion?()
})
}
func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
guard let component = view.component, let parentController = component.controller() else {
return
}
let disposable = self.resolvePeerByNameDisposable
var resolveSignal = component.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10)
var cancelImpl: (() -> Void)?
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { [weak parentController] subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
parentController?.present(controller, in: .window(.root))
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
resolveSignal = resolveSignal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = { [weak self] in
guard let self else {
return
}
self.resolvePeerByNameDisposable.set(nil)
}
disposable.set((resolveSignal
|> take(1)
|> mapToSignal { peer -> Signal<Peer?, NoError> in
return .single(peer?._asPeer())
}
|> deliverOnMainQueue).start(next: { [weak view] peer in
guard let view, let component = view.component else {
return
}
if let peer = peer {
var navigation = navigation
if case .default = navigation {
if let peer = peer as? TelegramUser, peer.botInfo != nil {
navigation = .chat(textInputState: nil, subject: nil, peekData: nil)
}
}
self.openResolved(view: view, result: .peer(peer, navigation))
} else {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
component.controller()?.present(textAlertController(context: component.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
}
func openHashtag(view: StoryItemSetContainerComponent.View, hashtag: String, peerName: String?) {
guard let component = view.component, let parentController = component.controller() else {
return
}
let peerId = component.slice.peer.id
var resolveSignal: Signal<Peer?, NoError>
if let peerName = peerName {
resolveSignal = component.context.engine.peers.resolvePeerByName(name: peerName)
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())
} else {
return .single(nil)
}
}
} else {
resolveSignal = component.context.account.postbox.loadedPeerWithId(peerId)
|> map(Optional.init)
}
var cancelImpl: (() -> Void)?
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { [weak parentController] subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
parentController?.present(controller, in: .window(.root))
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
resolveSignal = resolveSignal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = { [weak self] in
guard let self else {
return
}
self.resolvePeerByNameDisposable.set(nil)
}
self.resolvePeerByNameDisposable.set((resolveSignal
|> deliverOnMainQueue).start(next: { [weak view] peer in
guard let view, let component = view.component else {
return
}
guard let navigationController = component.controller()?.navigationController as? NavigationController else {
return
}
if !hashtag.isEmpty {
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag)
navigationController.pushViewController(searchController)
}
}))
}
func openPeerMention(view: StoryItemSetContainerComponent.View, peerId: EnginePeer.Id) {
guard let component = view.component else {
return
}
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self, weak view] peer in
guard let self, let view, let peer else {
return
}
self.openPeer(view: view, peer: peer)
})
}
func openPeer(view: StoryItemSetContainerComponent.View, peer: EnginePeer, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil) {
guard let component = view.component else {
return
}
let peerSignal: Signal<Peer?, NoError> = component.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init)
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak view] peer in
guard let view, let component = view.component, let peer else {
return
}
let mode: PeerInfoControllerMode = .generic
var expandAvatar = expandAvatar
if peer.smallProfileImage == nil {
expandAvatar = false
}
if component.metrics.widthClass == .regular {
expandAvatar = false
}
if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false, requestsContext: nil) {
component.controller()?.push(infoController)
}
}))
}
} }

View File

@ -1073,6 +1073,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
listContext.state, listContext.state,
self.focusedIdUpdated.get() self.focusedIdUpdated.get()
) )
//|> delay(0.4, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] peerAndVoiceMessages, state, _ in |> deliverOnMainQueue).start(next: { [weak self] peerAndVoiceMessages, state, _ in
guard let self else { guard let self else {
return return

View File

@ -148,7 +148,7 @@ final class StoryItemContentComponent: Component {
return return
} }
if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()) { if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()), !"".isEmpty {
if self.videoNode == nil { if self.videoNode == nil {
let videoNode = UniversalVideoNode( let videoNode = UniversalVideoNode(
postbox: component.context.account.postbox, postbox: component.context.account.postbox,

View File

@ -240,7 +240,8 @@ public final class StoryPeerListComponent: Component {
}, },
updateView: { view, state, transition in updateView: { view, state, transition in
(view as? StoryPeerListItemComponent.TransitionView)?.update(state: state, transition: transition) (view as? StoryPeerListItemComponent.TransitionView)?.update(state: state, transition: transition)
} },
insertCloneTransitionView: nil
)) ))
} }
} }

View File

@ -697,8 +697,9 @@ public final class StoryPeerListItemComponent: Component {
var titleTransition = transition var titleTransition = transition
if previousComponent?.ringAnimation != nil && component.ringAnimation == nil { if previousComponent?.ringAnimation != nil && component.ringAnimation == nil {
if let titleView = self.title.view, let snapshotView = titleView.snapshotContentTree() { if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
titleView.superview?.addSubview(snapshotView) self.button.addSubview(snapshotView)
snapshotView.frame = titleView.frame
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview() snapshotView?.removeFromSuperview()
}) })

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "arrowshape_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,167 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.866699 4.701172 cm
0.000000 0.000000 0.000000 scn
11.694376 14.486328 m
11.694376 13.656328 l
12.152772 13.656328 12.524376 14.027931 12.524376 14.486328 c
11.694376 14.486328 l
h
11.694376 6.236328 m
12.524376 6.236328 l
12.524376 6.456457 12.436930 6.667571 12.281274 6.823226 c
12.125619 6.978882 11.914505 7.066328 11.694376 7.066328 c
11.694376 6.236328 l
h
1.306852 1.959093 m
0.679964 2.503071 l
1.306852 1.959093 l
h
0.098354 2.362061 m
-0.727694 2.442957 l
0.098354 2.362061 l
h
13.546710 1.785391 m
12.979038 2.390907 l
13.546710 1.785391 l
h
21.838385 11.163817 m
22.406055 11.769334 l
21.838385 11.163817 l
h
21.838385 9.558837 m
22.406055 8.953321 l
21.838385 9.558837 l
h
12.524376 14.486328 m
12.524376 18.134773 l
10.864376 18.134773 l
10.864376 14.486328 l
12.524376 14.486328 l
h
12.979040 18.331747 m
21.270714 10.558302 l
22.406055 11.769334 l
14.114382 19.542780 l
12.979040 18.331747 l
h
21.270712 10.164352 m
12.979038 2.390907 l
14.114381 1.179874 l
22.406055 8.953321 l
21.270712 10.164352 l
h
12.524376 2.587883 m
12.524376 6.236328 l
10.864376 6.236328 l
10.864376 2.587883 l
12.524376 2.587883 l
h
11.694376 7.066328 m
5.780328 7.066328 2.387692 4.471083 0.679964 2.503071 c
1.933740 1.415115 l
3.380624 3.082527 6.338683 5.406328 11.694376 5.406328 c
11.694376 7.066328 l
h
0.924402 2.281166 m
1.118631 4.264515 1.688284 7.135999 3.270899 9.490906 c
4.820140 11.796155 7.367803 13.656328 11.694376 13.656328 c
11.694376 15.316328 l
6.783209 15.316328 3.732595 13.153936 1.893128 10.416837 c
0.087034 7.729396 -0.522455 4.538746 -0.727694 2.442957 c
0.924402 2.281166 l
h
0.679964 2.503071 m
0.721193 2.550583 0.766227 2.565825 0.782870 2.569241 c
0.790647 2.570837 0.793910 2.570457 0.793002 2.570513 c
0.792788 2.570526 0.792269 2.570576 0.791612 2.570700 c
0.790952 2.570822 0.790647 2.570927 0.790832 2.570868 c
0.791008 2.570812 0.792187 2.570429 0.794370 2.569412 c
0.796545 2.568398 0.800247 2.566505 0.805242 2.563288 c
0.814777 2.557144 0.832552 2.543856 0.852494 2.519463 c
0.873059 2.494308 0.894952 2.458349 0.909617 2.412121 c
0.924611 2.364859 0.928116 2.319090 0.924402 2.281166 c
-0.727694 2.442957 l
-0.807448 1.628555 -0.199440 1.144114 0.287034 0.989164 c
0.769746 0.835413 1.467871 0.878242 1.933740 1.415115 c
0.679964 2.503071 l
h
12.979038 2.390907 m
12.806601 2.229246 12.524376 2.351511 12.524376 2.587883 c
10.864376 2.587883 l
10.864376 0.898287 12.881754 0.024286 14.114381 1.179874 c
12.979038 2.390907 l
h
21.270714 10.558302 m
21.384493 10.451633 21.384495 10.271024 21.270712 10.164352 c
22.406055 8.953321 l
23.219378 9.715812 23.219381 11.006841 22.406055 11.769334 c
21.270714 10.558302 l
h
12.524376 18.134773 m
12.524376 18.371141 12.806600 18.493410 12.979040 18.331747 c
14.114382 19.542780 l
12.881757 20.698366 10.864376 19.824371 10.864376 18.134773 c
12.524376 18.134773 l
h
f
n
Q
endstream
endobj
3 0 obj
2921
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003011 00000 n
0000003034 00000 n
0000003207 00000 n
0000003281 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3340
%%EOF

View File

@ -91,6 +91,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
let fromScale: CGFloat = 1.0 let fromScale: CGFloat = 1.0
let scale = toScale.interpolate(to: fromScale, amount: state.progress) let scale = toScale.interpolate(to: fromScale, amount: state.progress)
transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0)) transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0))
},
insertCloneTransitionView: { view in
params.addToTransitionSurface(view)
} }
), ),
destinationRect: selectedTransitionNode.1, destinationRect: selectedTransitionNode.1,

View File

@ -37,6 +37,7 @@ import TextFormat
import ChatTextLinkEditUI import ChatTextLinkEditUI
import AttachmentTextInputPanelNode import AttachmentTextInputPanelNode
import ChatEntityKeyboardInputNode import ChatEntityKeyboardInputNode
import HashtagSearchUI
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() let subscribers = Bag<(Bool) -> Void>()
@ -1718,6 +1719,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return inputPanelNode return inputPanelNode
} }
public func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController {
return HashtagSearchController(context: context, peer: peer, query: query)
}
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController { public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
let mappedSource: PremiumSource let mappedSource: PremiumSource
switch source { switch source {

View File

@ -140,6 +140,9 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
text = strings.Message_Theme text = strings.Message_Theme
} else if content.type == "video" { } else if content.type == "video" {
text = stringForMediaKind(.video, strings: self.strings).0.string text = stringForMediaKind(.video, strings: self.strings).0.string
} else if content.type == "telegram_story" {
//TODO:localize
text = "Story"
} else if let _ = content.image { } else if let _ = content.image {
text = stringForMediaKind(.image, strings: self.strings).0.string text = stringForMediaKind(.image, strings: self.strings).0.string
} }