From 332408acb7c6d55af92a545dfd1ce77682bd5203 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 26 Jul 2024 00:06:19 +0200 Subject: [PATCH] Various fixes --- .../BrowserAddressListItemComponent.swift | 59 +++++++++- .../BrowserUI/Sources/BrowserScreen.swift | 107 ++++++++++++------ .../Sources/BrowserToolbarComponent.swift | 54 +++++---- .../BrowserUI/Sources/BrowserWebContent.swift | 49 +++++++- .../TelegramUI/Sources/ChatController.swift | 30 +++-- 5 files changed, 226 insertions(+), 73 deletions(-) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index 30ad83d58b..23a3604a0e 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -10,6 +10,9 @@ import TelegramPresentationData import PhotoResources import AccountContext +private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold) +private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) + final class BrowserAddressListItemComponent: Component { let context: AccountContext let theme: PresentationTheme @@ -57,6 +60,7 @@ final class BrowserAddressListItemComponent: Component { private let containerButton: HighlightTrackingButton private var emptyIcon: UIImageView? + private var emptyLabel: ComponentView? private var icon = TransformImageNode() private let title = ComponentView() private let subtitle = ComponentView() @@ -104,12 +108,14 @@ final class BrowserAddressListItemComponent: Component { let title: String let subtitle: String + var parsedUrl: URL? var iconImageReferenceAndRepresentation: (AnyMediaReference, TelegramMediaImageRepresentation)? var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? if case let .Loaded(content) = component.webPage.content { title = content.title ?? content.url subtitle = content.url + parsedUrl = URL(string: content.url) if let image = content.image { if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 80, height: 80)) { @@ -211,17 +217,58 @@ final class BrowserAddressListItemComponent: Component { iconImageApply() -// if strongSelf.iconTextBackgroundNode.supernode != nil { -// strongSelf.iconTextBackgroundNode.removeFromSupernode() -// } -// if strongSelf.iconTextNode.supernode != nil { -// strongSelf.iconTextNode.removeFromSupernode() -// } + if let emptyIcon = self.emptyIcon { + self.emptyIcon = nil + emptyIcon.removeFromSuperview() + } + if let emptyLabel = self.emptyLabel { + self.emptyLabel = nil + emptyLabel.view?.removeFromSuperview() + } } else { if self.icon.supernode != nil { self.icon.view.removeFromSuperview() } + let icon: UIImageView + let label: ComponentView + if let currentEmptyIcon = self.emptyIcon, let currentEmptyLabel = self.emptyLabel { + icon = currentEmptyIcon + label = currentEmptyLabel + } else { + icon = UIImageView() + icon.image = iconTextBackgroundImage + self.addSubview(icon) + + label = ComponentView() + } + icon.frame = iconFrame + + var iconText = "" + if let parsedUrl, let host = parsedUrl.host { + if parsedUrl.path.hasPrefix("/addstickers/") { + iconText = "S" + } else if parsedUrl.path.hasPrefix("/addemoji/") { + iconText = "E" + } else { + iconText = host[..? var navigationLeftItems: [AnyComponentWithIdentity] @@ -148,7 +149,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) ) ] - + if isTablet { navigationLeftItems.append( AnyComponentWithIdentity( @@ -273,24 +274,26 @@ private final class BrowserScreenComponent: CombinedComponent { ), at: 0 ) - navigationRightItems.append( - AnyComponentWithIdentity( - id: "openIn", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Instant View/Browser", - tintColor: environment.theme.rootController.navigationBar.accentTextColor - ) - ), - action: { - performAction.invoke(.openIn) - } + if canOpenIn { + navigationRightItems.append( + AnyComponentWithIdentity( + id: "openIn", + component: AnyComponent( + Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/Browser", + tintColor: environment.theme.rootController.navigationBar.accentTextColor + ) + ), + action: { + performAction.invoke(.openIn) + } + ) ) ) ) - ) + } } } } @@ -349,6 +352,7 @@ private final class BrowserScreenComponent: CombinedComponent { textColor: environment.theme.rootController.navigationBar.primaryTextColor, canGoBack: context.component.contentState?.canGoBack ?? false, canGoForward: context.component.contentState?.canGoForward ?? false, + canOpenIn: canOpenIn, performAction: performAction, performHoldAction: performHoldAction ) @@ -395,6 +399,12 @@ private final class BrowserScreenComponent: CombinedComponent { } if context.component.presentationState.addressFocused { + let addressListSize: CGSize + if isTablet { + addressListSize = CGSize(width: 660.0, height: 420.0) + } else { + addressListSize = CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize) + } let addressList = addressList.update( component: BrowserAddressListComponent( context: context.component.context, @@ -405,15 +415,26 @@ private final class BrowserScreenComponent: CombinedComponent { performAction.invoke(.navigateTo(url)) } ), - availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize), + availableSize: addressListSize, transition: context.transition ) - context.add(addressList - .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0)) - .clipsToBounds(true) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) - ) + + if isTablet { + context.add(addressList + .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0 - 3.0)) + .cornerRadius(10.0) + .clipsToBounds(true) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } else { + context.add(addressList + .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0)) + .clipsToBounds(true) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } } return context.availableSize @@ -992,7 +1013,7 @@ public class BrowserScreen: ViewController, MinimizableController { let _ = (settings |> deliverOnMainQueue).start(next: { [weak self] settings in - guard let self, let controller = self.controller, let contentState = self.contentState else { + guard let self, let controller = self.controller, let contentState = self.contentState, let layout = self.validLayout?.0 else { return } @@ -1072,22 +1093,26 @@ public class BrowserScreen: ViewController, MinimizableController { }))) } - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in - performAction.invoke(.share) - action(.default) - }))) + if !layout.metrics.isTablet { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in + performAction.invoke(.share) + action(.default) + }))) + } if [.webPage, .instantPage].contains(contentState.contentType) { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in performAction.invoke(.addBookmark) action(.default) }))) - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in - if let self { - self.context.sharedContext.applicationBindings.openUrl(openInUrl) - } - action(.default) - }))) + if !layout.metrics.isTablet { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in + if let self { + self.context.sharedContext.applicationBindings.openUrl(openInUrl) + } + action(.default) + }))) + } } let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items)))) @@ -1278,7 +1303,7 @@ public class BrowserScreen: ViewController, MinimizableController { BrowserContentComponent( content: content, insets: UIEdgeInsets( - top: environment.statusBarHeight, + top: layout.statusBarHeight ?? 0.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right @@ -1401,6 +1426,16 @@ public class BrowserScreen: ViewController, MinimizableController { } } + public override func dismiss(completion: (() -> Void)? = nil) { + if let layout = self.validLayout, layout.metrics.isTablet { + self.node.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in + super.dismiss(completion: completion) + }) + } else { + super.dismiss(completion: completion) + } + } + public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -1519,7 +1554,7 @@ private final class BrowserContentComponent: Component { } let collapsedHeight: CGFloat = 24.0 - let topInset: CGFloat = component.insets.top + component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + collapsedHeight * component.scrollingPanelOffsetFraction + let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction let bottomInset = (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right) let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: 49.0 + component.insets.bottom, right: component.insets.right) diff --git a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift index 2c35387e73..4c1e2c5a32 100644 --- a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift @@ -124,6 +124,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { let textColor: UIColor let canGoBack: Bool let canGoForward: Bool + let canOpenIn: Bool let performAction: ActionSlot let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void @@ -132,6 +133,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { textColor: UIColor, canGoBack: Bool, canGoForward: Bool, + canOpenIn: Bool, performAction: ActionSlot, performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void ) { @@ -139,6 +141,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { self.textColor = textColor self.canGoBack = canGoBack self.canGoForward = canGoForward + self.canOpenIn = canOpenIn self.performAction = performAction self.performHoldAction = performHoldAction } @@ -156,6 +159,9 @@ final class NavigationToolbarContentComponent: CombinedComponent { if lhs.canGoForward != rhs.canGoForward { return false } + if lhs.canOpenIn != rhs.canOpenIn { + return false + } return true } @@ -170,10 +176,16 @@ final class NavigationToolbarContentComponent: CombinedComponent { let availableSize = context.availableSize let performAction = context.component.performAction let performHoldAction = context.component.performHoldAction - + let sideInset: CGFloat = 5.0 let buttonSize = CGSize(width: 50.0, height: availableSize.height) - let spacing = (availableSize.width - buttonSize.width * 5.0 - sideInset * 2.0) / 4.0 + + var buttonCount = 4 + if context.component.canOpenIn { + buttonCount += 1 + } + + let spacing = (availableSize.width - buttonSize.width * CGFloat(buttonCount) - sideInset * 2.0) / CGFloat(buttonCount - 1) let canGoBack = context.component.canGoBack let back = back.update( @@ -269,24 +281,26 @@ final class NavigationToolbarContentComponent: CombinedComponent { .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width / 2.0, y: availableSize.height / 2.0)) ) - let openIn = openIn.update( - component: Button( - content: AnyComponent( - BundleIconComponent( - name: "Instant View/Browser", - tintColor: context.component.accentColor - ) - ), - action: { - performAction.invoke(.openIn) - } - ).minSize(buttonSize), - availableSize: buttonSize, - transition: .easeInOut(duration: 0.2) - ) - context.add(openIn - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0)) - ) + if context.component.canOpenIn { + let openIn = openIn.update( + component: Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/Browser", + tintColor: context.component.accentColor + ) + ), + action: { + performAction.invoke(.openIn) + } + ).minSize(buttonSize), + availableSize: buttonSize, + transition: .easeInOut(duration: 0.2) + ) + context.add(openIn + .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0)) + ) + } return availableSize } diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 632ebe486a..5a3d848674 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -182,6 +182,11 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU configuration.mediaPlaybackRequiresUserAction = false } + let contentController = WKUserContentController() + let videoScript = WKUserScript(source: videoSource, injectionTime: .atDocumentStart, forMainFrameOnly: false) + contentController.addUserScript(videoScript) + configuration.userContentController = contentController + self.webView = WebView(frame: CGRect(), configuration: configuration) self.webView.allowsLinkPreview = true @@ -601,7 +606,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU if download { decisionHandler(.download, preferences) } else { - decisionHandler(.cancel, preferences) +// decisionHandler(.cancel, preferences) } }) } else { @@ -1147,6 +1152,48 @@ let setupFontFunctions = """ })(); """ +private let videoSource = """ +function disableWebkitEnterFullscreen(videoElement) { + if (videoElement && videoElement.webkitEnterFullscreen) { + Object.defineProperty(videoElement, 'webkitEnterFullscreen', { + value: undefined + }); + } +} + +function disableFullscreenOnExistingVideos() { + document.querySelectorAll('video').forEach(disableWebkitEnterFullscreen); +} + +function handleMutations(mutations) { + mutations.forEach((mutation) => { + if (mutation.addedNodes && mutation.addedNodes.length > 0) { + mutation.addedNodes.forEach((newNode) => { + if (newNode.tagName === 'VIDEO') { + disableWebkitEnterFullscreen(newNode); + } + if (newNode.querySelectorAll) { + newNode.querySelectorAll('video').forEach(disableWebkitEnterFullscreen); + } + }); + } + }); +} + +disableFullscreenOnExistingVideos(); + +const observer = new MutationObserver(handleMutations); + +observer.observe(document.body, { + childList: true, + subtree: true +}); + +function disconnectObserver() { + observer.disconnect(); +} +""" + @available(iOS 16.0, *) final class BrowserSearchOptions: UITextSearchOptions { override var wordMatchMethod: UITextSearchOptions.WordMatchMethod { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 08bd4688f1..b397f693cd 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -245,6 +245,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var botStart: ChatControllerInitialBotStart? var attachBotStart: ChatControllerInitialAttachBotStart? var botAppStart: ChatControllerInitialBotAppStart? + let mode: ChatControllerPresentationMode let peerDisposable = MetaDisposable() let titleDisposable = MetaDisposable() @@ -658,6 +659,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.botStart = botStart self.attachBotStart = attachBotStart self.botAppStart = botAppStart + self.mode = mode self.peekData = peekData self.currentChatListFilter = chatListFilter self.chatNavigationStack = chatNavigationStack @@ -6548,6 +6550,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return value - 1 } + self.hasBrowserOrAppInFront.set(.single(false)) + let deallocate: () -> Void = { self.historyStateDisposable?.dispose() self.messageIndexDisposable.dispose() @@ -7133,18 +7137,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let hasBrowserOrWebAppInFront: Signal = .single([]) - |> then( - self.effectiveNavigationController?.viewControllersSignal ?? .single([]) - ) - |> map { controllers in - if controllers.last is BrowserScreen || controllers.last is AttachmentController { - return true - } else { - return false + if case .standard(.default) = self.mode { + let hasBrowserOrWebAppInFront: Signal = .single([]) + |> then( + self.effectiveNavigationController?.viewControllersSignal ?? .single([]) + ) + |> map { controllers in + if controllers.last is BrowserScreen || controllers.last is AttachmentController { + return true + } else { + return false + } } + self.hasBrowserOrAppInFront.set(hasBrowserOrWebAppInFront) } - self.hasBrowserOrAppInFront.set(hasBrowserOrWebAppInFront) } var returnInputViewFocus = false @@ -7634,6 +7640,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let _ = self.peekData { self.peekTimerDisposable.set(nil) } + + if case .standard(.default) = self.mode { + self.hasBrowserOrAppInFront.set(.single(false)) + } } func saveInterfaceState(includeScrollState: Bool = true) {