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 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 makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
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>
|
||||
|
@ -218,6 +218,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource)
|
||||
|
||||
self.accessoryPanelContainer = ASDisplayNode()
|
||||
|
||||
self.tabBarItemContextActionType = .always
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
@ -2275,7 +2277,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
var primaryContent: ChatListHeaderComponent.Content?
|
||||
if let primaryContext = self.primaryContext {
|
||||
var backTitle: String?
|
||||
if let previousItem = self.navigationBar?.previousItem {
|
||||
if let previousItem = self.previousItem {
|
||||
switch previousItem {
|
||||
case let .item(item):
|
||||
backTitle = item.title ?? self.presentationData.strings.Common_Back
|
||||
|
@ -1884,6 +1884,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
uploadProgress: self.controller?.storyUploadProgress,
|
||||
tabsNode: tabsNode,
|
||||
tabsNodeIsSearch: tabsNodeIsSearch,
|
||||
accessoryPanelContainer: self.controller?.accessoryPanelContainer,
|
||||
accessoryPanelContainerHeight: self.controller?.accessoryPanelContainerHeight ?? 0.0,
|
||||
activateSearch: { [weak self] searchContentNode in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
|
@ -373,6 +373,8 @@ final class ContactsControllerNode: ASDisplayNode {
|
||||
uploadProgress: nil,
|
||||
tabsNode: tabsNode,
|
||||
tabsNodeIsSearch: tabsNodeIsSearch,
|
||||
accessoryPanelContainer: nil,
|
||||
accessoryPanelContainerHeight: 0.0,
|
||||
activateSearch: { [weak self] searchContentNode in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -325,11 +325,14 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
if i == 0 {
|
||||
if canBeClosed {
|
||||
controllers[i].transitionNavigationBar?.previousItem = .close
|
||||
controllers[i].previousItem = .close
|
||||
} else {
|
||||
controllers[i].transitionNavigationBar?.previousItem = nil
|
||||
controllers[i].previousItem = nil
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
public internal(set) var previousItem: NavigationPreviousAction?
|
||||
|
||||
open var navigationPresentation: ViewControllerNavigationPresentation = .default
|
||||
open var _presentedInModal: Bool = false
|
||||
|
||||
|
@ -61,6 +61,9 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer
|
||||
open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
private let context: AccountContext
|
||||
|
||||
public var accessoryPanelContainer: ASDisplayNode?
|
||||
public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0
|
||||
|
||||
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
||||
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||
public let groupCallPanelSource: GroupCallPanelSource
|
||||
@ -76,7 +79,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?
|
||||
public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem?
|
||||
|
||||
public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
||||
public private(set) var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
||||
|
||||
private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode?
|
||||
private var locationBroadcastPeers: [EnginePeer]?
|
||||
@ -84,7 +87,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
||||
|
||||
private var groupCallPanelData: GroupCallPanelData?
|
||||
private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
||||
public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
||||
|
||||
private var dismissingPanel: ASDisplayNode?
|
||||
|
||||
@ -96,14 +99,16 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
override open var additionalNavigationBarHeight: CGFloat {
|
||||
var height: CGFloat = 0.0
|
||||
if let _ = self.groupCallAccessoryPanel {
|
||||
height += 50.0
|
||||
}
|
||||
if let _ = self.mediaAccessoryPanel {
|
||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
}
|
||||
if let _ = self.locationBroadcastAccessoryPanel {
|
||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
if self.accessoryPanelContainer == nil {
|
||||
if let _ = self.groupCallAccessoryPanel {
|
||||
height += 50.0
|
||||
}
|
||||
if let _ = self.mediaAccessoryPanel {
|
||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
}
|
||||
if let _ = self.locationBroadcastAccessoryPanel {
|
||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
}
|
||||
}
|
||||
return height
|
||||
}
|
||||
@ -401,16 +406,38 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
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 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 {
|
||||
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
|
||||
panelStartY += panelHeight
|
||||
|
||||
let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel
|
||||
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)
|
||||
)
|
||||
})
|
||||
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel)
|
||||
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
||||
accessoryPanelContainer.addSubnode(groupCallAccessoryPanel)
|
||||
} else {
|
||||
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel)
|
||||
}
|
||||
self.groupCallAccessoryPanel = groupCallAccessoryPanel
|
||||
groupCallAccessoryPanel.frame = panelFrame
|
||||
|
||||
@ -452,8 +483,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
|
||||
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
||||
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
|
||||
panelStartY += panelHeight
|
||||
|
||||
let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel
|
||||
if let current = self.locationBroadcastAccessoryPanel {
|
||||
@ -576,7 +608,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
@ -848,9 +877,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
}
|
||||
mediaAccessoryPanel.frame = panelFrame
|
||||
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 {
|
||||
self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel)
|
||||
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
||||
accessoryPanelContainer.addSubnode(mediaAccessoryPanel)
|
||||
} else {
|
||||
self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel)
|
||||
}
|
||||
}
|
||||
self.mediaAccessoryPanel = (mediaAccessoryPanel, type)
|
||||
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.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
}
|
||||
|
||||
self.accessoryPanelContainerHeight = additionalHeight
|
||||
}
|
||||
|
||||
open var keyShortcuts: [KeyShortcut] {
|
||||
|
@ -25,6 +25,8 @@ public final class ChatListNavigationBar: Component {
|
||||
public let uploadProgress: Float?
|
||||
public let tabsNode: ASDisplayNode?
|
||||
public let tabsNodeIsSearch: Bool
|
||||
public let accessoryPanelContainer: ASDisplayNode?
|
||||
public let accessoryPanelContainerHeight: CGFloat
|
||||
public let activateSearch: (NavigationBarSearchContentNode) -> Void
|
||||
public let openStatusSetup: (UIView) -> Void
|
||||
|
||||
@ -44,6 +46,8 @@ public final class ChatListNavigationBar: Component {
|
||||
uploadProgress: Float?,
|
||||
tabsNode: ASDisplayNode?,
|
||||
tabsNodeIsSearch: Bool,
|
||||
accessoryPanelContainer: ASDisplayNode?,
|
||||
accessoryPanelContainerHeight: CGFloat,
|
||||
activateSearch: @escaping (NavigationBarSearchContentNode) -> Void,
|
||||
openStatusSetup: @escaping (UIView) -> Void
|
||||
) {
|
||||
@ -62,6 +66,8 @@ public final class ChatListNavigationBar: Component {
|
||||
self.uploadProgress = uploadProgress
|
||||
self.tabsNode = tabsNode
|
||||
self.tabsNodeIsSearch = tabsNodeIsSearch
|
||||
self.accessoryPanelContainer = accessoryPanelContainer
|
||||
self.accessoryPanelContainerHeight = accessoryPanelContainerHeight
|
||||
self.activateSearch = activateSearch
|
||||
self.openStatusSetup = openStatusSetup
|
||||
}
|
||||
@ -112,6 +118,12 @@ public final class ChatListNavigationBar: Component {
|
||||
if lhs.tabsNodeIsSearch != rhs.tabsNodeIsSearch {
|
||||
return false
|
||||
}
|
||||
if lhs.accessoryPanelContainer !== rhs.accessoryPanelContainer {
|
||||
return false
|
||||
}
|
||||
if lhs.accessoryPanelContainerHeight != rhs.accessoryPanelContainerHeight {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -199,7 +211,7 @@ public final class ChatListNavigationBar: Component {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -317,6 +329,9 @@ public final class ChatListNavigationBar: Component {
|
||||
if component.tabsNode != nil {
|
||||
searchFrame.origin.y -= 40.0
|
||||
}
|
||||
if !component.isSearchActive {
|
||||
searchFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
|
||||
let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, 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))
|
||||
if !component.isSearchActive {
|
||||
tabsFrame.origin.y -= component.accessoryPanelContainerHeight
|
||||
}
|
||||
if component.tabsNode != nil {
|
||||
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 {
|
||||
disappearingTabsView.layer.anchorPoint = CGPoint()
|
||||
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))
|
||||
}
|
||||
|
||||
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?) {
|
||||
@ -464,6 +501,8 @@ public final class ChatListNavigationBar: Component {
|
||||
uploadProgress: storyUploadProgress,
|
||||
tabsNode: component.tabsNode,
|
||||
tabsNodeIsSearch: component.tabsNodeIsSearch,
|
||||
accessoryPanelContainer: component.accessoryPanelContainer,
|
||||
accessoryPanelContainerHeight: component.accessoryPanelContainerHeight,
|
||||
activateSearch: component.activateSearch,
|
||||
openStatusSetup: component.openStatusSetup
|
||||
)
|
||||
@ -547,6 +586,10 @@ public final class ChatListNavigationBar: Component {
|
||||
contentHeight += 40.0
|
||||
}
|
||||
|
||||
if component.accessoryPanelContainer != nil && !component.isSearchActive {
|
||||
contentHeight += component.accessoryPanelContainerHeight
|
||||
}
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: contentHeight)
|
||||
self.currentLayout = CurrentLayout(size: size)
|
||||
|
||||
|
@ -16,7 +16,7 @@ private extension MessageInputActionButtonComponent.Mode {
|
||||
case .attach:
|
||||
return "Chat/Input/Text/IconAttachment"
|
||||
case .forward:
|
||||
return "Chat/Input/Text/IconForward"
|
||||
return "Chat/Input/Text/IconForwardSend"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -889,6 +889,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
private var presentationData: PresentationData
|
||||
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?) {
|
||||
self.context = context
|
||||
@ -952,6 +954,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
}
|
||||
|
||||
if self.pendingOpenListContext != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:selection
|
||||
let listContext = PeerStoryListContentContextImpl(
|
||||
context: self.context,
|
||||
@ -959,6 +965,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
listContext: self.listSource,
|
||||
initialId: item.story.id
|
||||
)
|
||||
self.pendingOpenListContext = listContext
|
||||
self.itemGrid.isUserInteractionEnabled = false
|
||||
|
||||
let _ = (listContext.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
@ -966,6 +975,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
return
|
||||
}
|
||||
|
||||
guard let pendingOpenListContext = self.pendingOpenListContext, pendingOpenListContext === listContext else {
|
||||
return
|
||||
}
|
||||
self.pendingOpenListContext = nil
|
||||
self.itemGrid.isUserInteractionEnabled = true
|
||||
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
|
||||
let story = item.story
|
||||
@ -1016,6 +1031,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
},
|
||||
updateView: { view, state, transition in
|
||||
(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),
|
||||
|
@ -56,6 +56,11 @@ swift_library(
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/ImageCompression",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/InvisibleInkDustNode",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/UrlEscaping",
|
||||
"//submodules/OverlayStatusController",
|
||||
],
|
||||
visibility = [
|
||||
"//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)
|
||||
if let itemSetComponentView = itemSetView.view.view {
|
||||
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
|
||||
if itemSetView.superview == nil {
|
||||
self.addSubview(itemSetView)
|
||||
}
|
||||
@ -866,6 +866,10 @@ private final class StoryContainerScreenComponent: Component {
|
||||
itemSetTransition.setBounds(view: itemSetView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
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.setBounds(view: itemSetComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
|
||||
@ -1037,13 +1041,16 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
||||
public final class TransitionView {
|
||||
public let makeView: () -> UIView
|
||||
public let updateView: (UIView, TransitionState, Transition) -> Void
|
||||
public let insertCloneTransitionView: ((UIView) -> Void)?
|
||||
|
||||
public init(
|
||||
makeView: @escaping () -> UIView,
|
||||
updateView: @escaping (UIView, TransitionState, Transition) -> Void
|
||||
updateView: @escaping (UIView, TransitionState, Transition) -> Void,
|
||||
insertCloneTransitionView: ((UIView) -> Void)?
|
||||
) {
|
||||
self.makeView = makeView
|
||||
self.updateView = updateView
|
||||
self.insertCloneTransitionView = insertCloneTransitionView
|
||||
}
|
||||
}
|
||||
|
||||
@ -1092,6 +1099,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var didAnimateIn: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private let focusedItemPromise = Promise<StoryId?>(nil)
|
||||
@ -1141,8 +1149,12 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
|
||||
componentView.animateIn()
|
||||
if !self.didAnimateIn {
|
||||
self.didAnimateIn = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
|
||||
componentView.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,23 @@ import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import TextNodeWithEntities
|
||||
import TextFormat
|
||||
import InvisibleInkDustNode
|
||||
import UrlEscaping
|
||||
|
||||
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 {
|
||||
fileprivate(set) var isExpanded: Bool = false
|
||||
|
||||
@ -25,20 +40,38 @@ final class StoryContentCaptionComponent: Component {
|
||||
}
|
||||
|
||||
let externalState: ExternalState
|
||||
let context: AccountContext
|
||||
let text: String
|
||||
let entities: [MessageTextEntity]
|
||||
let action: (Action) -> Void
|
||||
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
text: String
|
||||
context: AccountContext,
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
action: @escaping (Action) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.action = action
|
||||
}
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
if lhs.entities != rhs.entities {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -70,7 +103,9 @@ final class StoryContentCaptionComponent: Component {
|
||||
private let shadowGradientLayer: SimpleGradientLayer
|
||||
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 weak var state: EmptyComponentState?
|
||||
@ -133,10 +168,10 @@ final class StoryContentCaptionComponent: Component {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if let textView = self.text.view {
|
||||
if let textView = self.textNode?.textNode.view {
|
||||
let textLocalPoint = self.convert(point, to: textView)
|
||||
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 {
|
||||
self.ignoreExternalState = true
|
||||
|
||||
@ -213,29 +345,64 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
let sideInset: CGFloat = 16.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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.text, font: Font.regular(16.0), textColor: .white)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: textContainerSize
|
||||
let attributedText = stringWithAppliedEntities(
|
||||
component.text,
|
||||
entities: component.entities,
|
||||
baseColor: .white,
|
||||
linkColor: .white,
|
||||
baseFont: Font.regular(16.0),
|
||||
linkFont: Font.regular(16.0),
|
||||
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 visibleTextHeight = min(maxHeight, textSize.height)
|
||||
let textOverflowHeight: CGFloat = textSize.height - visibleTextHeight
|
||||
let visibleTextHeight = min(maxHeight, textLayout.0.size.height)
|
||||
let textOverflowHeight: CGFloat = textLayout.0.size.height - visibleTextHeight
|
||||
let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
|
||||
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.scrollView.addSubview(textView)
|
||||
let textNode = textLayout.1(TextNodeWithEntities.Arguments(
|
||||
context: component.context,
|
||||
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(
|
||||
containerSize: availableSize,
|
||||
|
@ -6,7 +6,6 @@ import AppBundle
|
||||
import ComponentDisplayAdapters
|
||||
import ReactionSelectionNode
|
||||
import EntityKeyboard
|
||||
import StoryFooterPanelComponent
|
||||
import MessageInputPanelComponent
|
||||
import TelegramPresentationData
|
||||
import SwiftSignalKit
|
||||
@ -22,6 +21,7 @@ import ImageCompression
|
||||
import ShareWithPeersScreen
|
||||
import PlainButtonComponent
|
||||
import TooltipUI
|
||||
import PresentationDataUtils
|
||||
|
||||
public final class StoryItemSetContainerComponent: Component {
|
||||
public final class ExternalState {
|
||||
@ -242,7 +242,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
var captionItem: CaptionItem?
|
||||
|
||||
let inputPanel = ComponentView<Empty>()
|
||||
let footerPanel = ComponentView<Empty>()
|
||||
let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
|
||||
|
||||
var displayViewList: Bool = false
|
||||
@ -274,6 +273,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
private weak var voiceMessagesRestrictedTooltipController: TooltipController?
|
||||
|
||||
let transitionCloneContainerView: UIView
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.sendMessageContext = StoryItemSetContainerSendMessage()
|
||||
|
||||
@ -294,6 +295,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.closeButton = HighlightableButton()
|
||||
self.closeButtonIconView = UIImageView()
|
||||
|
||||
self.transitionCloneContainerView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
@ -648,8 +651,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
if component.slice.peer.id == component.context.account.peerId {
|
||||
self.displayViewList = true
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
|
||||
self.displayViewList = true
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
} else {
|
||||
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||
inputPanelView.activateInput()
|
||||
@ -670,16 +675,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
)
|
||||
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 {
|
||||
viewListView.layer.animatePosition(
|
||||
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) {
|
||||
self.closeButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
var cleanups: [() -> Void] = []
|
||||
|
||||
if let inputPanelView = self.inputPanel.view {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
viewListView.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
@ -811,39 +793,75 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if let rightInfoView = self.rightInfoItem?.view.view {
|
||||
if transitionOut.destinationIsAvatar {
|
||||
let transitionView = transitionOut.transitionView
|
||||
let transitionViewImpl = transitionView?.makeView()
|
||||
if let transitionViewImpl {
|
||||
self.insertSubview(transitionViewImpl, aboveSubview: self.contentContainerView)
|
||||
|
||||
var transitionViewsImpl: [UIView] = []
|
||||
|
||||
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 positionKeyframes: [CGPoint] = generateParabollicMotionKeyframes(from: sourceLocalFrame.center, to: rightInfoSourceFrame.center, elevation: 0.0, duration: 0.3, curve: .spring, reverse: true)
|
||||
|
||||
transitionViewImpl.frame = rightInfoSourceFrame
|
||||
transitionViewImpl.alpha = 0.0
|
||||
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
|
||||
sourceSize: rightInfoSourceFrame.size,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
progress: 0.0
|
||||
), .immediate)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
transitionViewImpl.frame = rightInfoSourceFrame
|
||||
transitionViewImpl.alpha = 0.0
|
||||
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
|
||||
sourceSize: rightInfoSourceFrame.size,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
progress: 0.0
|
||||
), .immediate)
|
||||
}
|
||||
|
||||
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
|
||||
|
||||
transitionViewImpl.alpha = 1.0
|
||||
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
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)
|
||||
|
||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||
}
|
||||
|
||||
transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1]
|
||||
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,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
progress: 1.0
|
||||
), transition)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
transitionViewImpl.layer.position = positionKeyframes[positionKeyframes.count - 1]
|
||||
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,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
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)
|
||||
@ -867,30 +885,63 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
if !transitionOut.destinationIsAvatar {
|
||||
let transitionView = transitionOut.transitionView
|
||||
let transitionViewImpl = transitionView?.makeView()
|
||||
if let transitionViewImpl {
|
||||
self.insertSubview(transitionViewImpl, belowSubview: self.contentContainerView)
|
||||
|
||||
var transitionViewsImpl: [UIView] = []
|
||||
|
||||
if let transitionViewImpl = transitionView?.makeView() {
|
||||
transitionViewsImpl.append(transitionViewImpl)
|
||||
|
||||
transitionViewImpl.frame = contentSourceFrame
|
||||
transitionViewImpl.alpha = 0.0
|
||||
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
|
||||
sourceSize: contentSourceFrame.size,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
progress: 0.0
|
||||
), .immediate)
|
||||
let transitionSourceContainerView = UIView(frame: self.bounds)
|
||||
transitionSourceContainerView.isUserInteractionEnabled = false
|
||||
self.insertSubview(transitionSourceContainerView, belowSubview: 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.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))
|
||||
|
||||
transitionViewImpl.alpha = 1.0
|
||||
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
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)
|
||||
|
||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
|
||||
sourceSize: contentSourceFrame.size,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
progress: 1.0
|
||||
), transition)
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||
transitionView?.updateView(transitionViewImpl, StoryContainerScreen.TransitionState(
|
||||
sourceSize: contentSourceFrame.size,
|
||||
destinationSize: sourceLocalFrame.size,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -1112,238 +1171,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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
|
||||
var viewListInset: CGFloat = 0.0
|
||||
|
||||
@ -1379,8 +1206,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let outerExpansionFraction: CGFloat
|
||||
if self.displayViewList {
|
||||
outerExpansionFraction = 1.0
|
||||
} else {
|
||||
} else if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
|
||||
outerExpansionFraction = component.verticalPanFraction
|
||||
} else {
|
||||
outerExpansionFraction = 0.0
|
||||
}
|
||||
|
||||
viewList.view.parentState = state
|
||||
@ -1762,7 +1591,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if let view = currentCenterInfoItem.view.view {
|
||||
var animateIn = false
|
||||
if view.superview == nil {
|
||||
self.contentContainerView.addSubview(view)
|
||||
self.contentContainerView.insertSubview(view, belowSubview: self.closeButton)
|
||||
animateIn = true
|
||||
}
|
||||
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,
|
||||
component: AnyComponent(StoryContentCaptionComponent(
|
||||
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: {},
|
||||
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
|
||||
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)
|
||||
|
@ -32,6 +32,8 @@ import TelegramPresentationData
|
||||
import ShareController
|
||||
import ChatPresentationInterfaceState
|
||||
import Postbox
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
|
||||
final class StoryItemSetContainerSendMessage {
|
||||
weak var attachmentController: AttachmentController?
|
||||
@ -46,6 +48,8 @@ final class StoryItemSetContainerSendMessage {
|
||||
var videoRecorder = Promise<InstantVideoController?>()
|
||||
let controllerNavigationDisposable = MetaDisposable()
|
||||
let enqueueMediaMessageDisposable = MetaDisposable()
|
||||
let navigationActionDisposable = MetaDisposable()
|
||||
let resolvePeerByNameDisposable = MetaDisposable()
|
||||
|
||||
private(set) var isMediaRecordingLocked: Bool = false
|
||||
var wasRecordingDismissed: Bool = false
|
||||
@ -53,6 +57,8 @@ final class StoryItemSetContainerSendMessage {
|
||||
deinit {
|
||||
self.controllerNavigationDisposable.dispose()
|
||||
self.enqueueMediaMessageDisposable.dispose()
|
||||
self.navigationActionDisposable.dispose()
|
||||
self.resolvePeerByNameDisposable.dispose()
|
||||
}
|
||||
|
||||
func performSendMessageAction(
|
||||
@ -1797,4 +1803,258 @@ final class StoryItemSetContainerSendMessage {
|
||||
let _ = (legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals)
|
||||
|> 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,
|
||||
self.focusedIdUpdated.get()
|
||||
)
|
||||
//|> delay(0.4, queue: .mainQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerAndVoiceMessages, state, _ in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -148,7 +148,7 @@ final class StoryItemContentComponent: Component {
|
||||
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 {
|
||||
let videoNode = UniversalVideoNode(
|
||||
postbox: component.context.account.postbox,
|
||||
|
@ -240,7 +240,8 @@ public final class StoryPeerListComponent: Component {
|
||||
},
|
||||
updateView: { view, state, transition in
|
||||
(view as? StoryPeerListItemComponent.TransitionView)?.update(state: state, transition: transition)
|
||||
}
|
||||
},
|
||||
insertCloneTransitionView: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -697,8 +697,9 @@ public final class StoryPeerListItemComponent: Component {
|
||||
|
||||
var titleTransition = transition
|
||||
if previousComponent?.ringAnimation != nil && component.ringAnimation == nil {
|
||||
if let titleView = self.title.view, let snapshotView = titleView.snapshotContentTree() {
|
||||
titleView.superview?.addSubview(snapshotView)
|
||||
if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
|
||||
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?.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 scale = toScale.interpolate(to: fromScale, amount: state.progress)
|
||||
transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0))
|
||||
},
|
||||
insertCloneTransitionView: { view in
|
||||
params.addToTransitionSurface(view)
|
||||
}
|
||||
),
|
||||
destinationRect: selectedTransitionNode.1,
|
||||
|
@ -37,6 +37,7 @@ import TextFormat
|
||||
import ChatTextLinkEditUI
|
||||
import AttachmentTextInputPanelNode
|
||||
import ChatEntityKeyboardInputNode
|
||||
import HashtagSearchUI
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1718,6 +1719,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
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 {
|
||||
let mappedSource: PremiumSource
|
||||
switch source {
|
||||
|
@ -140,6 +140,9 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
text = strings.Message_Theme
|
||||
} else if content.type == "video" {
|
||||
text = stringForMediaKind(.video, strings: self.strings).0.string
|
||||
} else if content.type == "telegram_story" {
|
||||
//TODO:localize
|
||||
text = "Story"
|
||||
} else if let _ = content.image {
|
||||
text = stringForMediaKind(.image, strings: self.strings).0.string
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user