mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
304 lines
12 KiB
Swift
304 lines
12 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import ComponentFlow
|
|
import TelegramCore
|
|
import PeerInfoVisualMediaPaneNode
|
|
import ViewControllerComponent
|
|
import ChatListHeaderComponent
|
|
import ContextUI
|
|
import ChatTitleView
|
|
import BottomButtonPanelComponent
|
|
import UndoUI
|
|
import MoreHeaderButton
|
|
import MediaEditorScreen
|
|
import SaveToCameraRoll
|
|
import ShareController
|
|
import OpenInExternalAppUI
|
|
|
|
final class StorySearchGridScreenComponent: Component {
|
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
|
|
|
let context: AccountContext
|
|
let scope: StorySearchControllerScope
|
|
let listContext: SearchStoryListContext?
|
|
|
|
init(
|
|
context: AccountContext,
|
|
scope: StorySearchControllerScope,
|
|
listContext: SearchStoryListContext?
|
|
) {
|
|
self.context = context
|
|
self.scope = scope
|
|
self.listContext = listContext
|
|
}
|
|
|
|
static func ==(lhs: StorySearchGridScreenComponent, rhs: StorySearchGridScreenComponent) -> Bool {
|
|
return true
|
|
}
|
|
|
|
final class View: UIView {
|
|
private var component: StorySearchGridScreenComponent?
|
|
private(set) weak var state: EmptyComponentState?
|
|
private var environment: EnvironmentType?
|
|
|
|
private(set) var paneNode: PeerInfoStoryPaneNode?
|
|
private var paneStatusDisposable: Disposable?
|
|
private(set) var paneStatusText: String?
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.paneStatusDisposable?.dispose()
|
|
}
|
|
|
|
func scrollToTop() {
|
|
guard let paneNode = self.paneNode else {
|
|
return
|
|
}
|
|
let _ = paneNode.scrollToTop()
|
|
}
|
|
|
|
private var isUpdating = false
|
|
func update(component: StorySearchGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
|
self.isUpdating = true
|
|
defer {
|
|
self.isUpdating = false
|
|
}
|
|
|
|
self.component = component
|
|
self.state = state
|
|
|
|
let sideInset: CGFloat = 14.0
|
|
let _ = sideInset
|
|
|
|
let environment = environment[EnvironmentType.self].value
|
|
|
|
let themeUpdated = self.environment?.theme !== environment.theme
|
|
|
|
self.environment = environment
|
|
|
|
if themeUpdated {
|
|
self.backgroundColor = environment.theme.list.plainBackgroundColor
|
|
}
|
|
|
|
let bottomInset: CGFloat = environment.safeInsets.bottom
|
|
|
|
let paneNode: PeerInfoStoryPaneNode
|
|
if let current = self.paneNode {
|
|
paneNode = current
|
|
} else {
|
|
let paneNodeScope: PeerInfoStoryPaneNode.Scope
|
|
switch component.scope {
|
|
case let .query(query):
|
|
paneNodeScope = .search(query: query)
|
|
case let .location(coordinates, venue):
|
|
paneNodeScope = .location(coordinates: coordinates, venue: venue)
|
|
}
|
|
|
|
paneNode = PeerInfoStoryPaneNode(
|
|
context: component.context,
|
|
scope: paneNodeScope,
|
|
captureProtected: false,
|
|
isProfileEmbedded: false,
|
|
canManageStories: false,
|
|
navigationController: { [weak self] in
|
|
guard let self else {
|
|
return nil
|
|
}
|
|
return self.environment?.controller()?.navigationController as? NavigationController
|
|
},
|
|
listContext: component.listContext
|
|
)
|
|
paneNode.parentController = environment.controller()
|
|
paneNode.isEmptyUpdated = { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if !self.isUpdating {
|
|
self.state?.updated(transition: .immediate)
|
|
}
|
|
}
|
|
self.paneNode = paneNode
|
|
self.addSubview(paneNode.view)
|
|
|
|
self.paneStatusDisposable = (paneNode.status
|
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if self.paneStatusText != status?.text {
|
|
self.paneStatusText = status?.text
|
|
(self.environment?.controller() as? StorySearchGridScreen)?.updateTitle()
|
|
}
|
|
})
|
|
}
|
|
|
|
paneNode.update(
|
|
size: availableSize,
|
|
topInset: environment.navigationHeight,
|
|
sideInset: environment.safeInsets.left,
|
|
bottomInset: bottomInset,
|
|
deviceMetrics: environment.deviceMetrics,
|
|
visibleHeight: availableSize.height,
|
|
isScrollingLockedAtTop: false,
|
|
expandProgress: 1.0,
|
|
navigationHeight: 0.0,
|
|
presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }),
|
|
synchronous: false,
|
|
transition: transition.containedViewLayoutTransition
|
|
)
|
|
transition.setFrame(view: paneNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
|
|
|
return availableSize
|
|
}
|
|
}
|
|
|
|
func makeView() -> View {
|
|
return View()
|
|
}
|
|
|
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|
|
|
|
public final class StorySearchGridScreen: ViewControllerComponentContainer {
|
|
private let context: AccountContext
|
|
private let scope: StorySearchControllerScope
|
|
private var isDismissed: Bool = false
|
|
|
|
private var titleView: ChatTitleView?
|
|
|
|
override public var additionalNavigationBarHeight: CGFloat {
|
|
if let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View, let paneNode = componentView.paneNode {
|
|
return paneNode.additionalNavigationHeight
|
|
} else {
|
|
return 0.0
|
|
}
|
|
}
|
|
|
|
public init(
|
|
context: AccountContext,
|
|
scope: StorySearchControllerScope,
|
|
listContext: SearchStoryListContext? = nil
|
|
) {
|
|
self.context = context
|
|
self.scope = scope
|
|
|
|
super.init(context: context, component: StorySearchGridScreenComponent(
|
|
context: context,
|
|
scope: scope,
|
|
listContext: listContext
|
|
), navigationBarAppearance: .default, theme: .default)
|
|
|
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
|
|
|
self.titleView = ChatTitleView(
|
|
context: context, theme:
|
|
presentationData.theme,
|
|
strings: presentationData.strings,
|
|
dateTimeFormat: presentationData.dateTimeFormat,
|
|
nameDisplayOrder: presentationData.nameDisplayOrder,
|
|
animationCache: context.animationCache,
|
|
animationRenderer: context.animationRenderer
|
|
)
|
|
self.titleView?.disableAnimations = true
|
|
|
|
self.navigationItem.titleView = self.titleView
|
|
|
|
if case .location = scope {
|
|
self.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed)), animated: true)
|
|
}
|
|
|
|
self.updateTitle()
|
|
|
|
self.scrollToTop = { [weak self] in
|
|
guard let self, let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View else {
|
|
return
|
|
}
|
|
componentView.scrollToTop()
|
|
}
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
}
|
|
|
|
@objc private func sharePressed() {
|
|
guard case let .location(_, venue) = self.scope else {
|
|
return
|
|
}
|
|
let locationMap = TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, venue: nil, address: venue.address, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
|
|
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
|
|
|
|
let shareAction = OpenInControllerAction(title: presentationData.strings.Conversation_ContextMenuShare, action: { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.present(ShareController(context: self.context, subject: .mapMedia(locationMap), externalShare: true), in: .window(.root), with: nil)
|
|
})
|
|
self.present(OpenInActionSheetController(context: self.context, updatedPresentationData: nil, item: .location(location: locationMap, directions: nil), additionalAction: shareAction, openUrl: { [weak self] url in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.context.sharedContext.applicationBindings.openUrl(url)
|
|
}), in: .window(.root), with: nil)
|
|
}
|
|
|
|
func updateTitle() {
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
guard let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View, let paneNode = componentView.paneNode else {
|
|
return
|
|
}
|
|
let _ = paneNode
|
|
|
|
let title: String?
|
|
if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
|
|
title = paneStatusText
|
|
} else {
|
|
title = nil
|
|
}
|
|
switch self.scope {
|
|
case let .query(query):
|
|
self.titleView?.titleContent = .custom("\(query)", title, false)
|
|
case .location:
|
|
self.titleView?.titleContent = .custom(presentationData.strings.StoryGridScreen_TitleLocationSearch, nil, false)
|
|
}
|
|
}
|
|
|
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
self.titleView?.layout = layout
|
|
}
|
|
}
|
|
|
|
private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource {
|
|
private let controller: ViewController
|
|
private let sourceNode: ContextReferenceContentNode
|
|
|
|
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
|
self.controller = controller
|
|
self.sourceNode = sourceNode
|
|
}
|
|
|
|
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
|
|
}
|
|
}
|