mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories
This commit is contained in:
parent
45a39e4d47
commit
39b395c2fa
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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] {
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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",
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "arrowshape_30.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
167
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf
vendored
Normal file
167
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForwardSend.imageset/arrowshape_30.pdf
vendored
Normal 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
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user