Web app improvements

This commit is contained in:
Ilya Laktyushin 2022-04-01 11:02:06 +04:00
parent abc50659d5
commit 54d0e64eac
15 changed files with 218 additions and 69 deletions

View File

@ -144,8 +144,11 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
return false return false
} }
var isTracking: Bool {
return self.panGestureArguments != nil
}
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)?
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) { @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard let (layout, controllers, coveredByModalTransition) = self.validLayout else { guard let (layout, controllers, coveredByModalTransition) = self.validLayout else {
return return

View File

@ -18,6 +18,7 @@ public enum AttachmentButtonType: Equatable {
case contact case contact
case poll case poll
case app(PeerId, String, TelegramMediaFile) case app(PeerId, String, TelegramMediaFile)
case standalone
} }
public protocol AttachmentContainable: ViewController { public protocol AttachmentContainable: ViewController {
@ -25,6 +26,7 @@ public protocol AttachmentContainable: ViewController {
var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void { get set } var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void { get set }
var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set }
var cancelPanGesture: () -> Void { get set } var cancelPanGesture: () -> Void { get set }
var isContainerPanning: () -> Bool { get set }
func resetForReuse() func resetForReuse()
func prepareForReuse() func prepareForReuse()
@ -349,6 +351,15 @@ public class AttachmentController: ViewController {
strongSelf.container.cancelPanGesture() strongSelf.container.cancelPanGesture()
} }
} }
controller.isContainerPanning = { [weak self] in
if let strongSelf = self {
return strongSelf.container.isTracking
} else {
return false
}
}
let previousController = strongSelf.currentControllers.last let previousController = strongSelf.currentControllers.last
strongSelf.currentControllers = [controller] strongSelf.currentControllers = [controller]
@ -557,14 +568,21 @@ public class AttachmentController: ViewController {
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
var containerInsets = containerLayout.intrinsicInsets var containerInsets = containerLayout.intrinsicInsets
containerInsets.bottom = panelHeight var hasPanel = false
if let controller = self.controller, controller.buttons.count > 1 {
hasPanel = true
containerInsets.bottom = panelHeight
}
let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets)
self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady {
self.wrapperNode.addSubnode(self.container) self.wrapperNode.addSubnode(self.container)
self.container.addSubnode(self.panel) if hasPanel {
self.container.addSubnode(self.panel)
}
self.animateIn() self.animateIn()
} }

View File

@ -184,6 +184,10 @@ private final class AttachButtonComponent: CombinedComponent {
name = appName name = appName
imageName = "" imageName = ""
imageFile = appIcon imageFile = appIcon
case .standalone:
name = ""
imageName = ""
imageFile = nil
} }
let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor

View File

@ -521,6 +521,7 @@ private class CreatePollControllerImpl: ItemListController, AttachmentContainabl
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { } public var cancelPanGesture: () -> Void = { }
public var isContainerPanning: () -> Bool = { return false }
} }
public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> AttachmentContainable { public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> AttachmentContainable {

View File

@ -409,6 +409,7 @@ open class LegacyController: ViewController, PresentableController, AttachmentCo
open var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } open var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
open var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } open var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
open var cancelPanGesture: () -> Void = { } open var cancelPanGesture: () -> Void = { }
open var isContainerPanning: () -> Bool = { return false }
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber)) self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber))

View File

@ -75,6 +75,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { } public var cancelPanGesture: () -> Void = { }
public var isContainerPanning: () -> Bool = { return false }
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, completion: @escaping (TelegramMediaMap, String?) -> Void) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, completion: @escaping (TelegramMediaMap, String?) -> Void) {
self.context = context self.context = context

View File

@ -133,6 +133,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { } public var cancelPanGesture: () -> Void = { }
public var isContainerPanning: () -> Bool = { return false }
var dismissAll: () -> Void = { } var dismissAll: () -> Void = { }

View File

@ -804,7 +804,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-440534818] = { return Api.Update.parse_updateUserStatus($0) } dict[-440534818] = { return Api.Update.parse_updateUserStatus($0) }
dict[-1071741569] = { return Api.Update.parse_updateUserTyping($0) } dict[-1071741569] = { return Api.Update.parse_updateUserTyping($0) }
dict[2139689491] = { return Api.Update.parse_updateWebPage($0) } dict[2139689491] = { return Api.Update.parse_updateWebPage($0) }
dict[-118080598] = { return Api.Update.parse_updateWebViewResultSent($0) } dict[361936797] = { return Api.Update.parse_updateWebViewResultSent($0) }
dict[2027216577] = { return Api.Updates.parse_updateShort($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) }
dict[1299050149] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[1299050149] = { return Api.Updates.parse_updateShortChatMessage($0) }
dict[826001400] = { return Api.Updates.parse_updateShortMessage($0) } dict[826001400] = { return Api.Updates.parse_updateShortMessage($0) }

View File

@ -605,7 +605,7 @@ public extension Api {
case updateUserStatus(userId: Int64, status: Api.UserStatus) case updateUserStatus(userId: Int64, status: Api.UserStatus)
case updateUserTyping(userId: Int64, action: Api.SendMessageAction) case updateUserTyping(userId: Int64, action: Api.SendMessageAction)
case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32)
case updateWebViewResultSent(peer: Api.Peer, botId: Int64, queryId: Int64) case updateWebViewResultSent(queryId: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -1473,12 +1473,10 @@ public extension Api {
serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(ptsCount, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false)
break break
case .updateWebViewResultSent(let peer, let botId, let queryId): case .updateWebViewResultSent(let queryId):
if boxed { if boxed {
buffer.appendInt32(-118080598) buffer.appendInt32(361936797)
} }
peer.serialize(buffer, true)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeInt64(queryId, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false)
break break
} }
@ -1684,8 +1682,8 @@ public extension Api {
return ("updateUserTyping", [("userId", String(describing: userId)), ("action", String(describing: action))]) return ("updateUserTyping", [("userId", String(describing: userId)), ("action", String(describing: action))])
case .updateWebPage(let webpage, let pts, let ptsCount): case .updateWebPage(let webpage, let pts, let ptsCount):
return ("updateWebPage", [("webpage", String(describing: webpage)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))]) return ("updateWebPage", [("webpage", String(describing: webpage)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))])
case .updateWebViewResultSent(let peer, let botId, let queryId): case .updateWebViewResultSent(let queryId):
return ("updateWebViewResultSent", [("peer", String(describing: peer)), ("botId", String(describing: botId)), ("queryId", String(describing: queryId))]) return ("updateWebViewResultSent", [("queryId", String(describing: queryId))])
} }
} }
@ -3449,19 +3447,11 @@ public extension Api {
} }
} }
public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? {
var _1: Api.Peer? var _1: Int64?
if let signature = reader.readInt32() { _1 = reader.readInt64()
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int64?
_2 = reader.readInt64()
var _3: Int64?
_3 = reader.readInt64()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil if _c1 {
let _c3 = _3 != nil return Api.Update.updateWebViewResultSent(queryId: _1!)
if _c1 && _c2 && _c3 {
return Api.Update.updateWebViewResultSent(peer: _1!, botId: _2!, queryId: _3!)
} }
else { else {
return nil return nil

View File

@ -1514,7 +1514,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate) updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate)
case .updateAttachMenuBots: case .updateAttachMenuBots:
updatedState.addUpdateAttachMenuBots() updatedState.addUpdateAttachMenuBots()
case let .updateWebViewResultSent(_, _, queryId): case let .updateWebViewResultSent(queryId):
updatedState.addDismissWebView(queryId) updatedState.addDismissWebView(queryId)
default: default:
break break

View File

@ -168,6 +168,7 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { } public var cancelPanGesture: () -> Void = { }
public var isContainerPanning: () -> Bool = { return false }
var delayDisappear = false var delayDisappear = false

View File

@ -3359,12 +3359,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil, replyToMessageId: nil, iconFile: nil)
controller.getNavigationController = { [weak self] in let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil)
return self?.effectiveNavigationController strongSelf.present(controller, in: .window(.root))
} // controller.getNavigationController = { [weak self] in
controller.navigationPresentation = .modal // return self?.effectiveNavigationController
strongSelf.push(controller) // }
// controller.navigationPresentation = .modal
// strongSelf.push(controller)
}, error: { [weak self] error in }, error: { [weak self] error in
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
@ -3380,12 +3382,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, replyToMessageId: nil, iconFile: nil) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal)
controller.getNavigationController = { [weak self] in strongSelf.present(controller, in: .window(.root))
return self?.effectiveNavigationController // let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, replyToMessageId: nil, iconFile: nil)
} // controller.getNavigationController = { [weak self] in
controller.navigationPresentation = .modal // return self?.effectiveNavigationController
strongSelf.push(controller) // }
// controller.navigationPresentation = .modal
// strongSelf.push(controller)
}, error: { [weak self] error in }, error: { [weak self] error in
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
@ -10870,6 +10874,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
completion(controller, nil) completion(controller, nil)
strongSelf.controllerNavigationDisposable.set(nil) strongSelf.controllerNavigationDisposable.set(nil)
default:
break
} }
} }
let present = { let present = {

View File

@ -79,6 +79,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
var cancelPanGesture: () -> Void = { } var cancelPanGesture: () -> Void = { }
var isContainerPanning: () -> Bool = { return false }
init(_ params: ContactSelectionControllerParams) { init(_ params: ContactSelectionControllerParams) {
self.context = params.context self.context = params.context

View File

@ -113,11 +113,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { } public var cancelPanGesture: () -> Void = { }
public var isContainerPanning: () -> Bool = { return false }
private class Node: ViewControllerTracingNode, WKNavigationDelegate, UIScrollViewDelegate { private class Node: ViewControllerTracingNode, WKNavigationDelegate, UIScrollViewDelegate {
private weak var controller: WebAppController? private weak var controller: WebAppController?
fileprivate var webView: WKWebView? fileprivate var webView: WebAppWebView?
private var placeholderIcon: UIImage? private var placeholderIcon: UIImage?
private var placeholderNode: ShimmerEffectNode? private var placeholderNode: ShimmerEffectNode?
@ -183,7 +184,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
configuration.mediaPlaybackRequiresUserAction = false configuration.mediaPlaybackRequiresUserAction = false
} }
let webView = WKWebView(frame: CGRect(), configuration: configuration) let webView = WebAppWebView(frame: CGRect(), configuration: configuration)
webView.alpha = 0.0 webView.alpha = 0.0
webView.isOpaque = false webView.isOpaque = false
webView.backgroundColor = .clear webView.backgroundColor = .clear
@ -200,6 +201,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
webView.allowsBackForwardNavigationGestures = false webView.allowsBackForwardNavigationGestures = false
webView.scrollView.delegate = self webView.scrollView.delegate = self
webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0)
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil) webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil)
webView.tintColor = self.presentationData.theme.rootController.tabBar.iconColor webView.tintColor = self.presentationData.theme.rootController.tabBar.iconColor
self.webView = webView self.webView = webView
@ -335,39 +337,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
private var animationProgress: CGFloat = 0.0 private var animationProgress: CGFloat = 0.0
private var floatSnapshotView: UIView?
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
if let webView = self.webView { if let webView = self.webView, let controller = self.controller {
let frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom))) let frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom)))
if case let .animated(duration, curve) = transition, let springAnimation = transition.animation(), webView.frame != frame {
let initial = webView.frame webView.updateFrame(frame: frame, panning: controller.isContainerPanning(), transition: transition)
let animation = POPBasicAnimation()
animation.property = (POPAnimatableProperty.property(withName: "frame", initializer: { property in
property?.readBlock = { node, values in
values?.pointee = (node as! Node).animationProgress
}
property?.writeBlock = { node, values in
(node as! Node).animationProgress = values!.pointee
var mappedValue = values!.pointee
switch curve {
case .spring:
mappedValue = springAnimationValueAt(springAnimation, mappedValue)
default:
break
}
let currentFrame = CGRect.interpolator()(initial, frame, mappedValue) as! CGRect
(node as! Node).webView?.frame = currentFrame
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
animation.fromValue = 0.0 as NSNumber
animation.toValue = 1.0 as NSNumber
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = duration * Double(springAnimation.speed)
self.pop_add(animation, forKey: "frame")
} else {
webView.frame = frame
}
} }
if let placeholderNode = self.placeholderNode { if let placeholderNode = self.placeholderNode {
@ -671,3 +650,11 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
} }
} }
public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, botId: PeerId, botName: String, url: String, queryId: Int64?, buttonText: String?, keepAliveSignal: Signal<Never, KeepWebViewError>?) -> ViewController {
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: peerId), buttons: [.standalone], initialButton: .standalone)
controller.requestController = { _, completion in
completion(WebAppController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, botId: botId, botName: botName, url: url, queryId: queryId, buttonText: buttonText, keepAliveSignal: keepAliveSignal, replyToMessageId: nil, iconFile: nil), nil)
}
return controller
}

View File

@ -0,0 +1,135 @@
import Foundation
import UIKit
import Display
import WebKit
import SwiftSignalKit
private let findFixedPositionClasses = """
function findFixedPositionClasses() {
var elems = document.body.getElementsByTagName("*");
var len = elems.length
var result = []
var j = 0;
for (var i = 0; i < len; i++) {
if ((window.getComputedStyle(elems[i],null).getPropertyValue('position') == 'fixed') && (window.getComputedStyle(elems[i],null).getPropertyValue('bottom') == '0px')) {
result[j] = elems[i].className;
j++;
}
}
return result;
}
findFixedPositionClasses();
"""
private func findFixedPositionViews(webView: WKWebView, classes: [String]) -> [(String, UIView)] {
if let contentView = webView.scrollView.subviews.first {
func recursiveSearch(_ view: UIView) -> [(String, UIView)] {
var result: [(String, UIView)] = []
let description = view.description
if description.contains("class='") {
for className in classes {
if description.contains(className) {
result.append((className, view))
break
}
}
}
for subview in view.subviews {
result.append(contentsOf: recursiveSearch(subview))
}
return result
}
return recursiveSearch(contentView)
} else {
return []
}
}
final class WebAppWebView: WKWebView {
private var fixedPositionClasses: [String] = []
private var currentFixedViews: [(String, UIView, UIView)] = []
private var timer: SwiftSignalKit.Timer?
deinit {
self.timer?.invalidate()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if self.timer == nil {
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.evaluateJavaScript(findFixedPositionClasses, completionHandler: { [weak self] result, _ in
if let result = result {
Queue.mainQueue().async {
self?.fixedPositionClasses = (result as? [String]) ?? []
}
}
})
}, queue: Queue.mainQueue())
timer.start()
self.timer = timer
}
}
func updateFrame(frame: CGRect, panning: Bool, transition: ContainedViewLayoutTransition) {
if panning {
let fixedPositionViews = findFixedPositionViews(webView: self, classes: self.fixedPositionClasses)
if fixedPositionViews.count != self.currentFixedViews.count {
for (_, view, snapshotView) in self.currentFixedViews {
view.alpha = 1.0
snapshotView.removeFromSuperview()
}
self.currentFixedViews = []
var updatedFixedViews: [(String, UIView, UIView)] = []
for (className, view) in fixedPositionViews {
if let snapshotView = view.snapshotView(afterScreenUpdates: false) {
updatedFixedViews.append((className, view, snapshotView))
self.addSubview(snapshotView)
}
}
self.currentFixedViews = updatedFixedViews
}
transition.updateFrame(view: self, frame: frame)
for (_, view, snapshotView) in self.currentFixedViews {
view.alpha = 0.0
var snapshotFrame = view.frame
snapshotFrame.origin.y = frame.height - snapshotFrame.height
transition.updateFrame(view: snapshotView, frame: snapshotFrame)
}
} else {
for (_, view, snapshotView) in self.currentFixedViews {
view.alpha = 0.0
var snapshotFrame = view.frame
snapshotFrame.origin.y = frame.height - snapshotFrame.height
transition.updateFrame(view: snapshotView, frame: snapshotFrame)
}
transition.updateFrame(view: self, frame: frame, completion: { _ in
for (_, view, snapshotView) in self.currentFixedViews {
view.alpha = 1.0
snapshotView.removeFromSuperview()
}
self.currentFixedViews = []
})
}
}
}