From 606e33607af9c6d56745efcbc5abf90b12a70df2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 25 Jul 2024 14:39:07 +0200 Subject: [PATCH 1/3] Browser improvements --- .../Sources/BrowserAddressBarComponent.swift | 13 +- .../Sources/BrowserAddressListComponent.swift | 2 +- .../BrowserUI/Sources/BrowserContent.swift | 4 +- .../Sources/BrowserDocumentContent.swift | 6 +- .../Sources/BrowserInstantPageContent.swift | 5 + .../BrowserNavigationBarComponent.swift | 23 +- .../BrowserUI/Sources/BrowserPdfContent.swift | 5 + .../BrowserUI/Sources/BrowserScreen.swift | 23 +- .../BrowserUI/Sources/BrowserWebContent.swift | 98 +++++- submodules/BrowserUI/Sources/Punycode.swift | 317 ++++++++++++++++++ .../Resources/WebEmbed/UIWebViewSearch.js | 9 +- submodules/TelegramUI/Sources/OpenUrl.swift | 7 +- 12 files changed, 483 insertions(+), 29 deletions(-) create mode 100644 submodules/BrowserUI/Sources/Punycode.swift diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index 679f3ea5c3..c326a203d5 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -206,9 +206,9 @@ final class AddressBarContentComponent: Component { self.activated(true) if let textField = self.textField { textField.becomeFirstResponder() - Queue.mainQueue().justDispatch { + Queue.mainQueue().after(0.3, { textField.selectAll(nil) - } + }) } } @@ -238,7 +238,9 @@ final class AddressBarContentComponent: Component { public func textFieldShouldReturn(_ textField: UITextField) -> Bool { if let component = self.component { - component.performAction.invoke(.navigateTo(explicitUrl(textField.text ?? ""))) + let finalUrl = explicitUrl(textField.text ?? "") +// finalUrl = finalUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? finalUrl + component.performAction.invoke(.navigateTo(finalUrl)) } textField.endEditing(true) return false @@ -273,6 +275,10 @@ final class AddressBarContentComponent: Component { var title: String = "" if let parsedUrl = URL(string: component.url) { title = parsedUrl.host ?? component.url + if title.hasPrefix("www.") { + title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4)) + } + title = title.idnaDecoded ?? title } self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, transition: transition) @@ -333,6 +339,7 @@ final class AddressBarContentComponent: Component { transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) + self.cancelButton.isUserInteractionEnabled = isActiveWithText let textX: CGFloat = backgroundFrame.minX + sideInset let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index b33621c4ee..34c91738e7 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -148,7 +148,7 @@ final class BrowserAddressListComponent: Component { } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - self.endEditing(true) + self.window?.endEditing(true) } private func updateScrolling(transition: ComponentTransition) { diff --git a/submodules/BrowserUI/Sources/BrowserContent.swift b/submodules/BrowserUI/Sources/BrowserContent.swift index 7096b83042..f9f1687d93 100644 --- a/submodules/BrowserUI/Sources/BrowserContent.swift +++ b/submodules/BrowserUI/Sources/BrowserContent.swift @@ -162,12 +162,14 @@ protocol BrowserContent: UIView { var present: (ViewController, Any?) -> Void { get set } var presentInGlobalOverlay: (ViewController) -> Void { get set } var getNavigationController: () -> NavigationController? { get set } + var openAppUrl: (String) -> Void { get set } var minimize: () -> Void { get set } var close: () -> Void { get set } var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set } - + func resetScrolling() + func reload() func stop() diff --git a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift index 26b1d11e7f..1fae1b7ad3 100644 --- a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift +++ b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift @@ -17,7 +17,6 @@ import ShareController import UndoUI import UrlEscaping - final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData @@ -37,6 +36,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate } var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -360,6 +360,10 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate } } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { // self.currentError = nil self.updateFontState(self.currentFontState, force: true) diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index 29bed73a96..a9972dbe90 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -66,6 +66,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var currentAccessibilityAreas: [AccessibilityAreaNode] = [] var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -766,6 +767,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.readingProgress.set(readingProgress) } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint { var contentOffset = CGPoint() for (_, itemNode) in self.visibleItemsWithNodes { diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index d31d7fd6a1..e153a02294 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -35,6 +35,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { let readingProgress: CGFloat let loadingProgress: Double? let collapseFraction: CGFloat + let activate: () -> Void init( backgroundColor: UIColor, @@ -50,7 +51,8 @@ final class BrowserNavigationBarComponent: CombinedComponent { centerItem: AnyComponentWithIdentity?, readingProgress: CGFloat, loadingProgress: Double?, - collapseFraction: CGFloat + collapseFraction: CGFloat, + activate: @escaping () -> Void ) { self.backgroundColor = backgroundColor self.separatorColor = separatorColor @@ -66,6 +68,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { self.readingProgress = readingProgress self.loadingProgress = loadingProgress self.collapseFraction = collapseFraction + self.activate = activate } static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool { @@ -122,6 +125,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self) + let activate = Child(Button.self) return { context in var availableWidth = context.availableSize.width @@ -266,6 +270,23 @@ final class BrowserNavigationBarComponent: CombinedComponent { ) } + if context.component.collapseFraction == 1.0 { + let activateAction = context.component.activate + let activate = activate.update( + component: Button( + content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))), + action: { + activateAction() + } + ), + availableSize: size, + transition: .immediate + ) + context.add(activate + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + ) + } + return size } } diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index 3bd36c1cf6..e037f45663 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -38,6 +38,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU } var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -352,6 +353,10 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { // self.currentError = nil self.updateFontState(self.currentFontState, force: true) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index daaad7f05c..b4ca2e6f30 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -188,7 +188,10 @@ private final class BrowserScreenComponent: CombinedComponent { centerItem: navigationContent, readingProgress: context.component.contentState?.readingProgress ?? 0.0, loadingProgress: context.component.contentState?.estimatedProgress, - collapseFraction: collapseFraction + collapseFraction: collapseFraction, + activate: { + performAction.invoke(.expand) + } ), availableSize: context.availableSize, transition: context.transition @@ -267,6 +270,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) 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)) ) @@ -314,6 +318,7 @@ public class BrowserScreen: ViewController, MinimizableController { case openAddressBar case closeAddressBar case navigateTo(String) + case expand } fileprivate final class Node: ViewControllerTracingNode { @@ -568,6 +573,10 @@ public class BrowserScreen: ViewController, MinimizableController { updatedState.addressFocused = false return updatedState }) + case .expand: + if let content = self.content.last { + content.resetScrolling() + } } } @@ -626,6 +635,14 @@ public class BrowserScreen: ViewController, MinimizableController { } self.pushContent(content, transition: .spring(duration: 0.4)) } + browserContent.openAppUrl = { [weak self] url in + guard let self else { + return + } + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: { [weak self] in + self?.view.window?.endEditing(true) + }) + } browserContent.present = { [weak self] c, a in guard let self, let controller = self.controller else { return @@ -989,6 +1006,10 @@ public class BrowserScreen: ViewController, MinimizableController { } } + if update.isReset { + scrollingPanelOffsetFraction = 0.0 + } + if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction { self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction self.requestLayout(transition: transition) diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index ba252fbbf7..58e21e3b0c 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -18,6 +18,7 @@ import UndoUI import LottieComponent import MultilineTextComponent import UrlEscaping +import UrlHandling private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { private final class PendingTask { @@ -139,6 +140,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU private let faviconDisposable = MetaDisposable() var pushContent: (BrowserScreen.Subject) -> Void = { _ in } + var openAppUrl: (String) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } var close: () -> Void = { } @@ -201,6 +203,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU super.init(frame: .zero) + self.backgroundColor = presentationData.theme.list.plainBackgroundColor + self.webView.backgroundColor = presentationData.theme.list.plainBackgroundColor + self.webView.allowsBackForwardNavigationGestures = true self.webView.scrollView.delegate = self self.webView.scrollView.clipsToBounds = false @@ -214,7 +219,6 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward), options: [], context: nil) self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.hasOnlySecureContent), options: [], context: nil) if #available(iOS 15.0, *) { - self.backgroundColor = presentationData.theme.list.plainBackgroundColor self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } if #available(iOS 16.4, *) { @@ -460,11 +464,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU ErrorComponent( theme: self.presentationData.theme, title: self.presentationData.strings.Browser_ErrorTitle, - text: error.localizedDescription + text: error.localizedDescription, + insets: insets ) ), environment: {}, - containerSize: CGSize(width: size.width - insets.left - insets.right - 72.0, height: size.height) + containerSize: CGSize(width: size.width, height: size.height) ) if self.errorView.superview == nil { self.addSubview(self.errorView) @@ -521,14 +526,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.snapScrollingOffsetToInsets() + + if self.ignoreUpdatesUntilScrollingStopped { + self.ignoreUpdatesUntilScrollingStopped = false + } } } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.snapScrollingOffsetToInsets() + + if self.ignoreUpdatesUntilScrollingStopped { + self.ignoreUpdatesUntilScrollingStopped = false + } } private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) { + guard !self.ignoreUpdatesUntilScrollingStopped else { + return + } let scrollView = self.webView.scrollView let isInteracting = scrollView.isDragging || scrollView.isDecelerating if let previousScrollingOffsetValue = self.previousScrollingOffset { @@ -558,6 +574,30 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } + private var ignoreUpdatesUntilScrollingStopped = false + func resetScrolling() { + self.updateScrollingOffset(isReset: true, transition: .spring(duration: 0.4)) + self.ignoreUpdatesUntilScrollingStopped = true + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let url = navigationAction.request.url?.absoluteString { + if isTelegramMeLink(url) || isTelegraPhLink(url) { + decisionHandler(.cancel) + self.minimize() + self.openAppUrl(url) + } else { + if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { + decisionHandler(.download) + } else { + decisionHandler(.allow) + } + } + } else { + decisionHandler(.allow) + } + } + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { self.currentError = nil self.updateFontState(self.currentFontState, force: true) @@ -881,15 +921,18 @@ private final class ErrorComponent: CombinedComponent { let theme: PresentationTheme let title: String let text: String + let insets: UIEdgeInsets init( theme: PresentationTheme, title: String, - text: String + text: String, + insets: UIEdgeInsets ) { self.theme = theme self.title = title self.text = text + self.insets = insets } static func ==(lhs: ErrorComponent, rhs: ErrorComponent) -> Bool { @@ -902,10 +945,14 @@ private final class ErrorComponent: CombinedComponent { if lhs.text != rhs.text { return false } + if lhs.insets != rhs.insets { + return false + } return true } static var body: Body { + let background = Child(Rectangle.self) let animation = Child(LottieComponent.self) let title = Child(MultilineTextComponent.self) let text = Child(MultilineTextComponent.self) @@ -916,6 +963,17 @@ private final class ErrorComponent: CombinedComponent { let animationSpacing: CGFloat = 8.0 let textSpacing: CGFloat = 8.0 + let constrainedWidth = context.availableSize.width - 76.0 - context.component.insets.left - context.component.insets.right + + let background = background.update( + component: Rectangle(color: context.component.theme.list.plainBackgroundColor), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + let animation = animation.update( component: LottieComponent( content: LottieComponent.AppBundleContent(name: "ChatListNoResults") @@ -924,9 +982,6 @@ private final class ErrorComponent: CombinedComponent { availableSize: CGSize(width: animationSize, height: animationSize), transition: .immediate ) - context.add(animation - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + animation.size.height / 2.0)) - ) contentHeight += animation.size.height + animationSpacing let title = title.update( @@ -939,12 +994,9 @@ private final class ErrorComponent: CombinedComponent { horizontalAlignment: .center ), environment: {}, - availableSize: context.availableSize, + availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height), transition: .immediate ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + title.size.height / 2.0)) - ) contentHeight += title.size.height + textSpacing let text = text.update( @@ -958,15 +1010,27 @@ private final class ErrorComponent: CombinedComponent { maximumNumberOfLines: 0 ), environment: {}, - availableSize: context.availableSize, + availableSize: CGSize(width: constrainedWidth, height: context.availableSize.height), transition: .immediate ) - context.add(text - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + text.size.height / 2.0)) - ) contentHeight += text.size.height - - return CGSize(width: context.availableSize.width, height: contentHeight) + + var originY = floor((context.availableSize.height - contentHeight) / 2.0) + context.add(animation + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + animation.size.height / 2.0)) + ) + originY += animation.size.height + animationSpacing + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + title.size.height / 2.0)) + ) + originY += title.size.height + textSpacing + + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + text.size.height / 2.0)) + ) + + return context.availableSize } } } diff --git a/submodules/BrowserUI/Sources/Punycode.swift b/submodules/BrowserUI/Sources/Punycode.swift new file mode 100644 index 0000000000..2edbbb22e3 --- /dev/null +++ b/submodules/BrowserUI/Sources/Punycode.swift @@ -0,0 +1,317 @@ +// +// Created by kojirof on 2018-11-19. +// Copyright (c) 2018 Gumob. All rights reserved. +// + +//MIT License +// +//Copyright (c) 2018 Gumob +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +import Foundation + +public class Punycode { + + /// Punycode RFC 3492 + /// See https://www.ietf.org/rfc/rfc3492.txt for standard details + + private let base: Int = 36 + private let tMin: Int = 1 + private let tMax: Int = 26 + private let skew: Int = 38 + private let damp: Int = 700 + private let initialBias: Int = 72 + private let initialN: Int = 128 + + /// RFC 3492 specific + private let delimiter: Character = "-" + private let lowercase: ClosedRange = "a"..."z" + private let digits: ClosedRange = "0"..."9" + private let lettersBase: UInt32 = Character("a").unicodeScalars.first!.value + private let digitsBase: UInt32 = Character("0").unicodeScalars.first!.value + + /// IDNA + private let ace: String = "xn--" + + private func adaptBias(_ delta: Int, _ numberOfPoints: Int, _ firstTime: Bool) -> Int { + var delta: Int = delta + if firstTime { + delta /= damp + } else { + delta /= 2 + } + delta += delta / numberOfPoints + var k: Int = 0 + while delta > ((base - tMin) * tMax) / 2 { + delta /= base - tMin + k += base + } + return k + ((base - tMin + 1) * delta) / (delta + skew) + } + + /// Maps a punycode character to index + private func punycodeIndex(for character: Character) -> Int? { + if lowercase.contains(character) { + return Int(character.unicodeScalars.first!.value - lettersBase) + } else if digits.contains(character) { + return Int(character.unicodeScalars.first!.value - digitsBase) + 26 /// count of lowercase letters range + } else { + return nil + } + } + + /// Maps an index to corresponding punycode character + private func punycodeValue(for digit: Int) -> Character? { + guard digit < base else { return nil } + if digit < 26 { + return Character(UnicodeScalar(lettersBase.advanced(by: digit))!) + } else { + return Character(UnicodeScalar(digitsBase.advanced(by: digit - 26))!) + } + } + + /// Decodes punycode encoded string to original representation + /// + /// - Parameter punycode: Punycode encoding (RFC 3492) + /// - Returns: Decoded string or nil if the input cannot be decoded + public func decodePunycode(_ punycode: Substring) -> String? { + var n: Int = initialN + var i: Int = 0 + var bias: Int = initialBias + var output: [Character] = [] + var inputPosition = punycode.startIndex + + let delimiterPosition: Substring.Index = punycode.lastIndex(of: delimiter) ?? punycode.startIndex + if delimiterPosition > punycode.startIndex { + output.append(contentsOf: punycode[..= bias + tMax ? tMax : k - bias) + if digit < t { + break + } + w *= base - t + k += base + } while !punycodeInput.isEmpty + bias = adaptBias(i - oldI, output.count + 1, oldI == 0) + n += i / (output.count + 1) + i %= (output.count + 1) + guard n >= 0x80, let scalar = UnicodeScalar(n) else { + return nil + } + output.insert(Character(scalar), at: i) + i += 1 + } + + return String(output) + } + + /// Encodes string to punycode (RFC 3492) + /// + /// - Parameter input: Input string + /// - Returns: Punycode encoded string + public func encodePunycode(_ input: Substring) -> String? { + var n: Int = initialN + var delta: Int = 0 + var bias: Int = initialBias + var output: String = "" + for scalar in input.unicodeScalars { + if scalar.isASCII { + let char = Character(scalar) + output.append(char) + } else if !scalar.isValid { + return nil /// Encountered a scalar out of acceptable range + } + } + var handled: Int = output.count + let basic: Int = handled + if basic > 0 { + output.append(delimiter) + } + while handled < input.unicodeScalars.count { + var minimumCodepoint: Int = 0x10FFFF + for scalar: Unicode.Scalar in input.unicodeScalars { + if scalar.value < minimumCodepoint && scalar.value >= n { + minimumCodepoint = Int(scalar.value) + } + } + delta += (minimumCodepoint - n) * (handled + 1) + n = minimumCodepoint + for scalar: Unicode.Scalar in input.unicodeScalars { + if scalar.value < n { + delta += 1 + } else if scalar.value == n { + var q: Int = delta + var k: Int = base + while true { + let t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias) + if q < t { + break + } + guard let character: Character = punycodeValue(for: t + ((q - t) % (base - t))) else { return nil } + output.append(character) + q = (q - t) / (base - t) + k += base + } + guard let character: Character = punycodeValue(for: q) else { return nil } + output.append(character) + bias = adaptBias(delta, handled + 1, handled == basic) + delta = 0 + handled += 1 + } + } + delta += 1 + n += 1 + } + + return output + } + + /// Returns new string containing IDNA-encoded hostname + /// + /// - Returns: IDNA encoded hostname or nil if the string can't be encoded + public func encodeIDNA(_ input: Substring) -> String? { + let parts: [Substring] = input.split(separator: ".") + var output: String = "" + for part: Substring in parts { + if output.count > 0 { + output.append(".") + } + if part.rangeOfCharacter(from: CharacterSet.urlHostAllowed.inverted) != nil { + guard let encoded: String = part.lowercased().punycodeEncoded else { return nil } + output += ace + encoded + } else { + output += part + } + } + return output + } + + /// Returns new string containing hostname decoded from IDNA representation + /// + /// - Returns: Original hostname or nil if the string doesn't contain correct encoding + public func decodedIDNA(_ input: Substring) -> String? { + let parts: [Substring] = input.split(separator: ".") + var output: String = "" + for part: Substring in parts { + if output.count > 0 { + output.append(".") + } + if part.hasPrefix(ace) { + guard let decoded: String = part.dropFirst(ace.count).punycodeDecoded else { return nil } + output += decoded + } else { + output += part + } + } + return output + } +} + +private extension Substring { + func lastIndex(of element: Character) -> String.Index? { + var position: Index = endIndex + while position > startIndex { + position = self.index(before: position) + if self[position] == element { + return position + } + } + return nil + } +} + +private extension UnicodeScalar { + var isValid: Bool { + return value < 0xD880 || (value >= 0xE000 && value <= 0x1FFFFF) + } +} + +public extension Substring { + /// Returns new string in punycode encoding (RFC 3492) + /// + /// - Returns: Punycode encoded string or nil if the string can't be encoded + var punycodeEncoded: String? { + return Punycode().encodePunycode(self) + } + + /// Returns new string decoded from punycode representation (RFC 3492) + /// + /// - Returns: Original string or nil if the string doesn't contain correct encoding + var punycodeDecoded: String? { + return Punycode().decodePunycode(self) + } + + /// Returns new string containing IDNA-encoded hostname + /// + /// - Returns: IDNA encoded hostname or nil if the string can't be encoded + var idnaEncoded: String? { + return Punycode().encodeIDNA(self) + } + + /// Returns new string containing hostname decoded from IDNA representation + /// + /// - Returns: Original hostname or nil if the string doesn't contain correct encoding + var idnaDecoded: String? { + return Punycode().decodedIDNA(self) + } +} + +public extension String { + + /// Returns new string in punycode encoding (RFC 3492) + /// + /// - Returns: Punycode encoded string or nil if the string can't be encoded + var punycodeEncoded: String? { + return self[..=0; i--) { uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword); } diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index c25368c74b..4897df2846 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1008,11 +1008,12 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur return } + let urlScheme = (parsedUrl.scheme ?? "").lowercased() var isInternetUrl = false - if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" { + if ["http", "https"].contains(urlScheme) { isInternetUrl = true } - if parsedUrl.scheme == "tonsite" { + if urlScheme == "tonsite" { isInternetUrl = true } @@ -1032,7 +1033,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur settings = .defaultSettings } if accessChallengeData.data.isLockable { - if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == nil { + if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == "inApp" { settings = WebBrowserSettings(defaultWebBrowser: "safari", exceptions: []) } } From 2eeb3be2b2f9b28f22aa47a6b4f6cd193348ea78 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 25 Jul 2024 16:15:45 +0200 Subject: [PATCH 2/3] Browser improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + submodules/BrowserUI/BUILD | 1 + .../Sources/BrowserAddressBarComponent.swift | 21 +++++- .../BrowserNavigationBarComponent.swift | 1 + .../BrowserUI/Sources/BrowserScreen.swift | 14 +++- .../BrowserUI/Sources/BrowserWebContent.swift | 75 ++++++++++++++++--- .../Components/MediaEditorScreen/BUILD | 1 + .../Sources/MediaEditorScreen.swift | 1 + .../PeerInfo/PeerInfoStoryGridScreen/BUILD | 1 + .../Sources/PeerInfoStoryGridScreen.swift | 2 +- .../Sources/StorySearchGridScreen.swift | 1 - .../Components/SaveProgressScreen/BUILD | 24 ++++++ .../Sources/SaveProgressScreen.swift | 0 .../Stories/StoryContainerScreen/BUILD | 1 + .../StoryItemSetContainerComponent.swift | 1 + 15 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 submodules/TelegramUI/Components/SaveProgressScreen/BUILD rename submodules/TelegramUI/Components/{MediaEditorScreen => SaveProgressScreen}/Sources/SaveProgressScreen.swift (100%) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2993d91058..5c30092651 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12602,3 +12602,6 @@ Sorry for the inconvenience."; "Story.Privacy.ChooseCoverInfo" = "Choose a frame from the story to show in your Profile."; "Story.Privacy.ChooseCoverChannelInfo" = "Choose a frame from the story to show in channel profile."; "Story.Privacy.ChooseCoverGroupInfo" = "Choose a frame from the story to show in group profile."; + +"WebBrowser.Download.Confirmation" = "Do you want to download \"%@\"?"; +"WebBrowser.Download.Download" = "Download"; diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index d143a87202..f03e31beb4 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode", "//submodules/SearchUI", "//submodules/SearchBarNode", + "//submodules/TelegramUI/Components/SaveProgressScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index c326a203d5..6ab928088a 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -280,6 +280,7 @@ final class AddressBarContentComponent: Component { } title = title.idnaDecoded ?? title } + self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, transition: transition) return availableSize @@ -438,7 +439,25 @@ final class AddressBarContentComponent: Component { textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) } - textField.text = self.component?.url ?? "" + var address = self.component?.url ?? "" + if let components = URLComponents(string: address) { + if #available(iOS 16.0, *), let encodedHost = components.encodedHost { + if let decodedHost = components.host, encodedHost != decodedHost { + address = address.replacingOccurrences(of: encodedHost, with: decodedHost) + } + } else if let encodedHost = components.host { + if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost { + address = address.replacingOccurrences(of: encodedHost, with: decodedHost) + } + } + } + + if textField.text != address { + textField.text = address + self.clearIconView.isHidden = address.isEmpty + self.clearIconButton.isHidden = address.isEmpty + self.placeholderContent.view?.isHidden = !address.isEmpty + } textField.textColor = theme.rootController.navigationSearchBar.inputTextColor transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height))) diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index e153a02294..1b0d09728c 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -245,6 +245,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { if !leftItemList.isEmpty || !rightItemList.isEmpty { availableWidth -= 20.0 } + availableWidth -= context.component.sideInset * 2.0 let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index b4ca2e6f30..661fbbeedb 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -1189,7 +1189,7 @@ public class BrowserScreen: ViewController, MinimizableController { private let context: AccountContext private let subject: Subject - var openPreviousOnClose = false + private var openPreviousOnClose = false private var validLayout: ContainerViewLayout? @@ -1205,9 +1205,10 @@ public class BrowserScreen: ViewController, MinimizableController { // "application/vnd.openxmlformats-officedocument.presentationml.presentation" ] - public init(context: AccountContext, subject: Subject) { + public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) { self.context = context self.subject = subject + self.openPreviousOnClose = openPreviousOnClose super.init(navigationBarPresentationData: nil) @@ -1243,9 +1244,18 @@ public class BrowserScreen: ViewController, MinimizableController { } public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) { + self.openPreviousOnClose = false self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity) } + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if self.openPreviousOnClose, let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer, let controller = minimizedContainer.controllers.last { + navigationController.maximizeViewController(controller, animated: true) + } + } + public var isMinimized = false { didSet { if let webContent = self.node.content.last as? BrowserWebContent { diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 58e21e3b0c..921447f526 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -19,6 +19,7 @@ import LottieComponent import MultilineTextComponent import UrlEscaping import UrlHandling +import SaveProgressScreen private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { private final class PendingTask { @@ -157,16 +158,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU let configuration = WKWebViewConfiguration() -// let bundle = Bundle.main -// let bundleVersion = bundle.infoDictionary?["CFBundleShortVersionString"] ?? "" -// var proxyServerHost = "magic.org" if let data = context.currentAppConfiguration.with({ $0 }).data, let hostValue = data["ton_proxy_address"] as? String { proxyServerHost = hostValue } configuration.setURLSchemeHandler(TonSchemeHandler(proxyServerHost: proxyServerHost), forURLScheme: "tonsite") configuration.allowsInlineMediaPlayback = true -// configuration.applicationNameForUserAgent = "Telegram-iOS/\(bundleVersion)" if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { configuration.mediaTypesRequiringUserActionForPlayback = [] } else { @@ -580,6 +577,41 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.ignoreUpdatesUntilScrollingStopped = true } + @available(iOS 13.0, *) + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) { + if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { + self.presentDownloadConfirmation(fileName: navigationAction.request.mainDocumentURL?.lastPathComponent ?? "file", proceed: { download in + if download { + decisionHandler(.download, preferences) + } else { + decisionHandler(.cancel, preferences) + } + }) + } else { + if let url = navigationAction.request.url?.absoluteString { + if isTelegramMeLink(url) || isTelegraPhLink(url) { + decisionHandler(.cancel, preferences) + self.minimize() + self.openAppUrl(url) + } else { + decisionHandler(.allow, preferences) + } + } else { + decisionHandler(.allow, preferences) + } + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + if navigationResponse.canShowMIMEType { + decisionHandler(.allow) + } else if #available(iOS 14.5, *) { + decisionHandler(.download) + } else { + decisionHandler(.cancel) + } + } + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url?.absoluteString { if isTelegramMeLink(url) || isTelegraPhLink(url) { @@ -587,11 +619,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.minimize() self.openAppUrl(url) } else { - if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { - decisionHandler(.download) - } else { - decisionHandler(.allow) - } + decisionHandler(.allow) } } else { decisionHandler(.allow) @@ -636,7 +664,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.close() } - @available(iOSApplicationExtension 15.0, iOS 15.0, *) + @available(iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.prompt) } @@ -739,12 +767,37 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU completionHandler(configuration) } + private func presentDownloadConfirmation(fileName: String, proceed: @escaping (Bool) -> Void) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + var completed = false + let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + if !completed { + completed = true + proceed(false) + } + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Download_Download, action: { + if !completed { + completed = true + proceed(true) + } + })]) + alertController.dismissed = { byOutsideTap in + if byOutsideTap { + if !completed { + completed = true + proceed(false) + } + } + } + self.present(alertController, nil) + } + private func open(url: String, new: Bool) { let subject: BrowserScreen.Subject = .webPage(url: url) if new, let navigationController = self.getNavigationController() { navigationController._keepModalDismissProgress = true self.minimize() - let controller = BrowserScreen(context: self.context, subject: subject) + let controller = BrowserScreen(context: self.context, subject: subject, openPreviousOnClose: true) navigationController._keepModalDismissProgress = true navigationController.pushViewController(controller) } else { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index c5f8979db7..e74d17b390 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -64,6 +64,7 @@ swift_library( "//submodules/WebsiteType", "//submodules/UrlEscaping", "//submodules/DeviceLocationManager", + "//submodules/TelegramUI/Components/SaveProgressScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 2bae267beb..4f2cf97358 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -48,6 +48,7 @@ import StickerPackEditTitleController import StickerPickerScreen import UIKitRuntimeUtils import ImageObjectSeparation +import SaveProgressScreen private let playbackButtonTag = GenericComponentViewTag() private let muteButtonTag = GenericComponentViewTag() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD index 5ba2d19928..b26f1644a1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD @@ -30,6 +30,7 @@ swift_library( "//submodules/SaveToCameraRoll", "//submodules/ShareController", "//submodules/OpenInExternalAppUI", + "//submodules/TelegramUI/Components/SaveProgressScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index e9fccb53a3..dbbf2ff7e3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -14,7 +14,7 @@ import ChatTitleView import BottomButtonPanelComponent import UndoUI import MoreHeaderButton -import MediaEditorScreen +import SaveProgressScreen import SaveToCameraRoll final class PeerInfoStoryGridScreenComponent: Component { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift index 6bdc9c2581..3cf859f663 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift @@ -14,7 +14,6 @@ import ChatTitleView import BottomButtonPanelComponent import UndoUI import MoreHeaderButton -import MediaEditorScreen import SaveToCameraRoll import ShareController import OpenInExternalAppUI diff --git a/submodules/TelegramUI/Components/SaveProgressScreen/BUILD b/submodules/TelegramUI/Components/SaveProgressScreen/BUILD new file mode 100644 index 0000000000..e9de562525 --- /dev/null +++ b/submodules/TelegramUI/Components/SaveProgressScreen/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SaveProgressScreen", + module_name = "SaveProgressScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/LottieAnimationComponent", + "//submodules/AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift b/submodules/TelegramUI/Components/SaveProgressScreen/Sources/SaveProgressScreen.swift similarity index 100% rename from submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift rename to submodules/TelegramUI/Components/SaveProgressScreen/Sources/SaveProgressScreen.swift diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 7e63c31eea..4d244dd6fc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -98,6 +98,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen", "//submodules/TelegramUI/Components/SliderContextItem", "//submodules/TelegramUI/Components/InteractiveTextComponent", + "//submodules/TelegramUI/Components/SaveProgressScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index a1f5b25732..16fb16ac43 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -43,6 +43,7 @@ import TelegramUIPreferences import StoryFooterPanelComponent import TelegramNotices import SliderContextItem +import SaveProgressScreen public final class StoryAvailableReactions: Equatable { let reactionItems: [ReactionItem] From e0f95989d422e5318eeecc50a26ad7d5a2a43f52 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 25 Jul 2024 17:04:44 +0200 Subject: [PATCH 3/3] Browser improvements --- .../Sources/BrowserAddressListComponent.swift | 9 +++++ .../BrowserAddressListItemComponent.swift | 10 ++++-- .../BrowserUI/Sources/BrowserContent.swift | 2 +- .../Sources/BrowserDocumentContent.swift | 4 +-- .../Sources/BrowserInstantPageContent.swift | 8 ++--- .../BrowserUI/Sources/BrowserPdfContent.swift | 4 +-- .../BrowserUI/Sources/BrowserScreen.swift | 5 +-- .../BrowserUI/Sources/BrowserWebContent.swift | 35 +++++++++++++------ .../Sources/SectionHeaderComponent.swift | 8 ++++- 9 files changed, 61 insertions(+), 24 deletions(-) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index 34c91738e7..2070587445 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -13,17 +13,20 @@ final class BrowserAddressListComponent: Component { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let insets: UIEdgeInsets let navigateTo: (String) -> Void init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, + insets: UIEdgeInsets, navigateTo: @escaping (String) -> Void ) { self.context = context self.theme = theme self.strings = strings + self.insets = insets self.navigateTo = navigateTo } @@ -37,6 +40,9 @@ final class BrowserAddressListComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.insets != rhs.insets { + return false + } return true } @@ -211,6 +217,7 @@ final class BrowserAddressListComponent: Component { theme: component.theme, style: .plain, title: sectionTitle, + insets: component.insets, actionTitle: section.id == 0 ? "Clear" : nil, action: { [weak self] in if let self, let component = self.component { @@ -292,6 +299,7 @@ final class BrowserAddressListComponent: Component { webPage: webPage!, message: itemMessage, hasNext: true, + insets: component.insets, action: { if let url = webPage?.content.url { navigateTo(url) @@ -393,6 +401,7 @@ final class BrowserAddressListComponent: Component { webPage: TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))), message: nil, hasNext: true, + insets: .zero, action: {} )), environment: {}, diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index 49ebcb2c20..30ad83d58b 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -16,6 +16,7 @@ final class BrowserAddressListItemComponent: Component { let webPage: TelegramMediaWebpage var message: Message? let hasNext: Bool + let insets: UIEdgeInsets let action: () -> Void init( @@ -24,6 +25,7 @@ final class BrowserAddressListItemComponent: Component { webPage: TelegramMediaWebpage, message: Message?, hasNext: Bool, + insets: UIEdgeInsets, action: @escaping () -> Void ) { self.context = context @@ -31,6 +33,7 @@ final class BrowserAddressListItemComponent: Component { self.webPage = webPage self.message = message self.hasNext = hasNext + self.insets = insets self.action = action } @@ -44,6 +47,9 @@ final class BrowserAddressListItemComponent: Component { if lhs.hasNext != rhs.hasNext { return false } + if lhs.insets != rhs.insets { + return false + } return true } @@ -92,7 +98,7 @@ final class BrowserAddressListItemComponent: Component { let iconSize = CGSize(width: 40.0, height: 40.0) let height: CGFloat = 60.0 - let leftInset: CGFloat = 11.0 + iconSize.width + 11.0 + let leftInset: CGFloat = component.insets.left + 11.0 + iconSize.width + 11.0 let rightInset: CGFloat = 16.0 let titleSpacing: CGFloat = 2.0 @@ -181,7 +187,7 @@ final class BrowserAddressListItemComponent: Component { } - let iconFrame = CGRect(origin: CGPoint(x: 11.0, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: 11.0 + component.insets.left, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize) let iconImageLayout = self.icon.asyncLayout() var iconImageApply: (() -> Void)? diff --git a/submodules/BrowserUI/Sources/BrowserContent.swift b/submodules/BrowserUI/Sources/BrowserContent.swift index f9f1687d93..7422bed87e 100644 --- a/submodules/BrowserUI/Sources/BrowserContent.swift +++ b/submodules/BrowserUI/Sources/BrowserContent.swift @@ -188,7 +188,7 @@ protocol BrowserContent: UIView { func addToRecentlyVisited() - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) func makeContentSnapshotView() -> UIView? } diff --git a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift index 1fae1b7ad3..316ab28538 100644 --- a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift +++ b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift @@ -101,7 +101,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } if let (size, insets, fullInsets) = self.validLayout { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate) + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate) } } @@ -240,7 +240,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate } private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)? - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) { + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) { self.validLayout = (size, insets, fullInsets) self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating) diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index a9972dbe90..2ceff5ec34 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -301,7 +301,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg guard let (size, insets, fullInsets) = self.containerLayout else { return } - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: transition) + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: transition) } func reload() { @@ -375,11 +375,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: transition.containedViewLayoutTransition) + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) { + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: transition.containedViewLayoutTransition) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { self.containerLayout = (size, insets, fullInsets) var updateVisibleItems = false diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index e037f45663..cb61825774 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -111,7 +111,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.backgroundColor = presentationData.theme.list.plainBackgroundColor } if let (size, insets, fullInsets) = self.validLayout { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate) + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate) } } @@ -250,7 +250,7 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU } private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)? - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) { + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) { self.validLayout = (size, insets, fullInsets) self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 661fbbeedb..e6f681aa45 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -261,6 +261,7 @@ private final class BrowserScreenComponent: CombinedComponent { context: context.component.context, theme: environment.theme, strings: environment.strings, + insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), navigateTo: { url in performAction.invoke(.navigateTo(url)) } @@ -1370,8 +1371,8 @@ private final class BrowserContentComponent: Component { 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) - - component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, transition: transition) + + component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, safeInsets: component.insets, transition: transition) transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize)) return availableSize diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 921447f526..6844c5929c 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -117,11 +117,23 @@ private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { } } +final class WebView: WKWebView { + var customBottomInset: CGFloat = 0.0 { + didSet { + self.setNeedsLayout() + } + } + + override var safeAreaInsets: UIEdgeInsets { + return UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.customBottomInset, right: 0.0) + } +} + final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData - let webView: WKWebView + let webView: WebView private let errorView: ComponentHostView private var currentError: Error? @@ -170,7 +182,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU configuration.mediaPlaybackRequiresUserAction = false } - self.webView = WKWebView(frame: CGRect(), configuration: configuration) + self.webView = WebView(frame: CGRect(), configuration: configuration) self.webView.allowsLinkPreview = true if #available(iOS 11.0, *) { @@ -245,8 +257,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.backgroundColor = presentationData.theme.list.plainBackgroundColor self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } - if let (size, insets, fullInsets) = self.validLayout { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate) + if let (size, insets, fullInsets, safeInsets) = self.validLayout { + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate) } } @@ -434,13 +446,13 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.webView.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.webView.scrollView.contentInset.top), animated: true) } - private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets)? - func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition) { - self.validLayout = (size, insets, fullInsets) + private var validLayout: (CGSize, UIEdgeInsets, UIEdgeInsets, UIEdgeInsets)? + func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, transition: ComponentTransition) { + self.validLayout = (size, insets, fullInsets, safeInsets) self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating) - let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - fullInsets.bottom)) + let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top)) var refresh = false if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width { refresh = true @@ -451,6 +463,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.webView.reloadInputViews() } + self.webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: fullInsets.bottom, right: 0.0) + self.webView.customBottomInset = max(insets.bottom, safeInsets.bottom) +// self.webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 34.0, right: 0.0) self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right) self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right) @@ -646,8 +661,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } else { self.currentError = nil } - if let (size, insets, fullInsets) = self.validLayout { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate) + if let (size, insets, fullInsets, safeInsets) = self.validLayout { + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate) } } diff --git a/submodules/BrowserUI/Sources/SectionHeaderComponent.swift b/submodules/BrowserUI/Sources/SectionHeaderComponent.swift index 6ebe3e80c9..294d5c1a34 100644 --- a/submodules/BrowserUI/Sources/SectionHeaderComponent.swift +++ b/submodules/BrowserUI/Sources/SectionHeaderComponent.swift @@ -13,6 +13,7 @@ final class SectionHeaderComponent: Component { let theme: PresentationTheme let style: Style let title: String + let insets: UIEdgeInsets let actionTitle: String? let action: (() -> Void)? @@ -20,12 +21,14 @@ final class SectionHeaderComponent: Component { theme: PresentationTheme, style: Style, title: String, + insets: UIEdgeInsets, actionTitle: String?, action: (() -> Void)? ) { self.theme = theme self.style = style self.title = title + self.insets = insets self.actionTitle = actionTitle self.action = action } @@ -40,6 +43,9 @@ final class SectionHeaderComponent: Component { if lhs.title != rhs.title { return false } + if lhs.insets != rhs.insets { + return false + } if lhs.actionTitle != rhs.actionTitle { return false } @@ -73,7 +79,7 @@ final class SectionHeaderComponent: Component { self.state = state let height: CGFloat = 28.0 - let leftInset: CGFloat = 16.0 + let leftInset: CGFloat = 16.0 + component.insets.left let rightInset: CGFloat = 0.0 let previousTitleFrame = self.title.view?.frame