mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '2b6636a0198c2ad8d80b67ef47a0402ee8bb6bd3'
This commit is contained in:
commit
8069dfa73a
@ -653,7 +653,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
||||
|
||||
strongSelf.frameUpdated(frame.index, frame.totalFrames)
|
||||
strongSelf.currentFrameIndex = frame.index
|
||||
strongSelf.currentFrameCount = frame.totalFrames;
|
||||
strongSelf.currentFrameCount = frame.totalFrames
|
||||
strongSelf.currentFrameRate = frameRate
|
||||
|
||||
if frame.isLastFrame {
|
||||
|
@ -18,6 +18,7 @@ import MinimizedContainer
|
||||
import InstantPageUI
|
||||
import NavigationStackComponent
|
||||
import LottieComponent
|
||||
import WebKit
|
||||
|
||||
private let settingsTag = GenericComponentViewTag()
|
||||
|
||||
@ -489,13 +490,13 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
case expand
|
||||
}
|
||||
|
||||
fileprivate final class Node: ViewControllerTracingNode {
|
||||
final class Node: ViewControllerTracingNode {
|
||||
private weak var controller: BrowserScreen?
|
||||
private let context: AccountContext
|
||||
|
||||
private let contentContainerView = UIView()
|
||||
fileprivate let contentNavigationContainer = ComponentView<Empty>()
|
||||
fileprivate var content: [BrowserContent] = []
|
||||
private(set) var content: [BrowserContent] = []
|
||||
fileprivate var contentState: BrowserContentState?
|
||||
private var contentStateDisposable = MetaDisposable()
|
||||
|
||||
@ -785,13 +786,14 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
let browserContent: BrowserContent
|
||||
switch content {
|
||||
case let .webPage(url):
|
||||
let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url)
|
||||
let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url, preferredConfiguration: self.controller?.preferredConfiguration)
|
||||
webContent.cancelInteractiveTransitionGestures = { [weak self] in
|
||||
if let self, let view = self.controller?.view {
|
||||
cancelInteractiveTransitionGestures(view: view)
|
||||
}
|
||||
}
|
||||
browserContent = webContent
|
||||
self.controller?.preferredConfiguration = nil
|
||||
case let .instantPage(webPage, anchor, sourceLocation):
|
||||
let instantPageContent = BrowserInstantPageContent(context: self.context, presentationData: self.presentationData, webPage: webPage, anchor: anchor, url: webPage.content.url ?? "", sourceLocation: sourceLocation)
|
||||
instantPageContent.openPeer = { [weak self] peer in
|
||||
@ -846,7 +848,9 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
return
|
||||
}
|
||||
if controller.isMinimized {
|
||||
|
||||
if let navigationController = controller.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
minimizedContainer.removeController(controller)
|
||||
}
|
||||
} else {
|
||||
controller.dismiss()
|
||||
}
|
||||
@ -1328,7 +1332,8 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
right: layout.safeInsets.right
|
||||
),
|
||||
navigationBarHeight: navigationBarHeight,
|
||||
scrollingPanelOffsetFraction: self.scrollingPanelOffsetFraction
|
||||
scrollingPanelOffsetFraction: self.scrollingPanelOffsetFraction,
|
||||
hasBottomPanel: !layout.metrics.isTablet || self.presentationState.isSearching
|
||||
)
|
||||
))
|
||||
)
|
||||
@ -1372,7 +1377,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
|
||||
private let context: AccountContext
|
||||
private let subject: Subject
|
||||
|
||||
private var preferredConfiguration: WKWebViewConfiguration?
|
||||
private var openPreviousOnClose = false
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
@ -1389,7 +1394,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
]
|
||||
|
||||
public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) {
|
||||
public init(context: AccountContext, subject: Subject, preferredConfiguration: WKWebViewConfiguration? = nil, openPreviousOnClose: Bool = false) {
|
||||
var subject = subject
|
||||
if case let .webPage(url) = subject, let parsedUrl = URL(string: url) {
|
||||
if parsedUrl.host?.hasSuffix(".ton") == true {
|
||||
@ -1402,6 +1407,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
}
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.preferredConfiguration = preferredConfiguration
|
||||
self.openPreviousOnClose = openPreviousOnClose
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
@ -1419,7 +1425,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
private var node: Node {
|
||||
var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
@ -1539,17 +1545,20 @@ private final class BrowserContentComponent: Component {
|
||||
let insets: UIEdgeInsets
|
||||
let navigationBarHeight: CGFloat
|
||||
let scrollingPanelOffsetFraction: CGFloat
|
||||
let hasBottomPanel: Bool
|
||||
|
||||
init(
|
||||
content: BrowserContent,
|
||||
insets: UIEdgeInsets,
|
||||
navigationBarHeight: CGFloat,
|
||||
scrollingPanelOffsetFraction: CGFloat
|
||||
scrollingPanelOffsetFraction: CGFloat,
|
||||
hasBottomPanel: Bool
|
||||
) {
|
||||
self.content = content
|
||||
self.insets = insets
|
||||
self.navigationBarHeight = navigationBarHeight
|
||||
self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
|
||||
self.hasBottomPanel = hasBottomPanel
|
||||
}
|
||||
|
||||
static func ==(lhs: BrowserContentComponent, rhs: BrowserContentComponent) -> Bool {
|
||||
@ -1565,6 +1574,9 @@ private final class BrowserContentComponent: Component {
|
||||
if lhs.scrollingPanelOffsetFraction != rhs.scrollingPanelOffsetFraction {
|
||||
return false
|
||||
}
|
||||
if lhs.hasBottomPanel != rhs.hasBottomPanel {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1584,9 +1596,9 @@ private final class BrowserContentComponent: Component {
|
||||
|
||||
let collapsedHeight: CGFloat = 24.0
|
||||
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 bottomInset = component.hasBottomPanel ? (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) : 0.0
|
||||
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)
|
||||
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: component.hasBottomPanel ? 49.0 + component.insets.bottom : 0.0, right: component.insets.right)
|
||||
|
||||
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))
|
||||
|
@ -203,40 +203,46 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
|
||||
private var tempFile: TempBoxFile?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, url: String) {
|
||||
init(context: AccountContext, presentationData: PresentationData, url: String, preferredConfiguration: WKWebViewConfiguration? = nil) {
|
||||
self.context = context
|
||||
self.uuid = UUID()
|
||||
self.presentationData = presentationData
|
||||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
|
||||
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
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
configuration.mediaTypesRequiringUserActionForPlayback = []
|
||||
} else {
|
||||
configuration.mediaPlaybackRequiresUserAction = false
|
||||
}
|
||||
|
||||
let contentController = WKUserContentController()
|
||||
let videoScript = WKUserScript(source: videoSource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(videoScript)
|
||||
let touchScript = WKUserScript(source: setupTouchObservers, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(touchScript)
|
||||
configuration.userContentController = contentController
|
||||
configuration.applicationNameForUserAgent = computedUserAgent()
|
||||
|
||||
var handleScriptMessageImpl: ((WKScriptMessage) -> Void)?
|
||||
let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(eventProxyScript)
|
||||
contentController.add(WeakScriptMessageHandler { message in
|
||||
handleScriptMessageImpl?(message)
|
||||
}, name: "performAction")
|
||||
|
||||
let configuration: WKWebViewConfiguration
|
||||
if let preferredConfiguration {
|
||||
configuration = preferredConfiguration
|
||||
} else {
|
||||
configuration = WKWebViewConfiguration()
|
||||
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
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
configuration.mediaTypesRequiringUserActionForPlayback = []
|
||||
} else {
|
||||
configuration.mediaPlaybackRequiresUserAction = false
|
||||
}
|
||||
|
||||
let contentController = WKUserContentController()
|
||||
let videoScript = WKUserScript(source: videoSource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(videoScript)
|
||||
let touchScript = WKUserScript(source: setupTouchObservers, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(touchScript)
|
||||
|
||||
let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
contentController.addUserScript(eventProxyScript)
|
||||
contentController.add(WeakScriptMessageHandler { message in
|
||||
handleScriptMessageImpl?(message)
|
||||
}, name: "performAction")
|
||||
|
||||
configuration.userContentController = contentController
|
||||
configuration.applicationNameForUserAgent = computedUserAgent()
|
||||
}
|
||||
|
||||
self.webView = WebView(frame: CGRect(), configuration: configuration)
|
||||
self.webView.allowsLinkPreview = true
|
||||
|
||||
@ -711,7 +717,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
self.minimize()
|
||||
self.openAppUrl(url)
|
||||
} else {
|
||||
decisionHandler(.allow, preferences)
|
||||
if let scheme = navigationAction.request.url?.scheme, !["http", "https", "tonsite", "about"].contains(scheme.lowercased()) {
|
||||
decisionHandler(.cancel, preferences)
|
||||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {})
|
||||
} else {
|
||||
decisionHandler(.allow, preferences)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decisionHandler(.allow, preferences)
|
||||
@ -786,7 +797,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
self.minimize()
|
||||
self.openAppUrl(url)
|
||||
} else {
|
||||
self.open(url: url, new: true)
|
||||
return self.open(url: url, configuration: configuration, new: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -927,17 +938,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
self.present(alertController, nil)
|
||||
}
|
||||
|
||||
private func open(url: String, new: Bool) {
|
||||
@discardableResult private func open(url: String, configuration: WKWebViewConfiguration? = nil, new: Bool) -> WKWebView? {
|
||||
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, openPreviousOnClose: true)
|
||||
let controller = BrowserScreen(context: self.context, subject: subject, preferredConfiguration: configuration, openPreviousOnClose: true)
|
||||
navigationController._keepModalDismissProgress = true
|
||||
navigationController.pushViewController(controller)
|
||||
return (controller.node.content.last as? BrowserWebContent)?.webView
|
||||
} else {
|
||||
self.pushContent(subject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func share(url: String) {
|
||||
|
@ -6,12 +6,15 @@ public protocol MinimizedContainer: ASDisplayNode {
|
||||
var controllers: [MinimizableController] { get }
|
||||
var isExpanded: Bool { get }
|
||||
|
||||
var willMaximize: (() -> Void)? { get set }
|
||||
var willMaximize: ((MinimizedContainer) -> Void)? { get set }
|
||||
var willDismiss: ((MinimizedContainer) -> Void)? { get set }
|
||||
var didDismiss: ((MinimizedContainer) -> Void)? { get set }
|
||||
|
||||
var statusBarStyle: StatusBarStyle { get }
|
||||
var statusBarStyleUpdated: (() -> Void)? { get set }
|
||||
|
||||
func addController(_ viewController: MinimizableController, topEdgeOffset: CGFloat?, beforeMaximize: @escaping (NavigationController, @escaping () -> Void) -> Void, transition: ContainedViewLayoutTransition)
|
||||
func removeController(_ viewController: MinimizableController)
|
||||
func maximizeController(_ viewController: MinimizableController, animated: Bool, completion: @escaping (Bool) -> Void)
|
||||
func collapse()
|
||||
func dismissAll(completion: @escaping () -> Void)
|
||||
|
@ -153,13 +153,23 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
open var minimizedContainer: MinimizedContainer? {
|
||||
didSet {
|
||||
self.minimizedContainer?.navigationController = self
|
||||
self.minimizedContainer?.willMaximize = { [weak self] in
|
||||
self.minimizedContainer?.willMaximize = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isMaximizing = true
|
||||
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
self.minimizedContainer?.willDismiss = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.minimizedContainer = nil
|
||||
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
self.minimizedContainer?.didDismiss = { minimizedContainer in
|
||||
minimizedContainer.removeFromSupernode()
|
||||
}
|
||||
self.minimizedContainer?.statusBarStyleUpdated = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -209,7 +209,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
|
||||
if !self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
string = self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
||||
} else {
|
||||
string = self.linkEntity.url.uppercased().replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "")
|
||||
string = self.linkEntity.url.uppercased().replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "tonsite://", with: "")
|
||||
}
|
||||
let text = NSMutableAttributedString(string: string)
|
||||
let range = NSMakeRange(0, text.length)
|
||||
|
@ -624,6 +624,15 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
}
|
||||
|
||||
func getRenderSubEntities() -> [DrawingEntity] {
|
||||
var explicitlyStaticStickers = Set<Int64>()
|
||||
if let customEmojiContainerView = self.customEmojiContainerView {
|
||||
for (key, view) in customEmojiContainerView.emojiLayers {
|
||||
if let view = view as? EmojiTextAttachmentView, let numFrames = view.contentLayer.numFrames, numFrames == 1 {
|
||||
explicitlyStaticStickers.insert(key.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let textSize = self.textView.bounds.size
|
||||
let textPosition = self.textEntity.position
|
||||
let scale = self.textEntity.scale
|
||||
@ -638,6 +647,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
|
||||
let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0)
|
||||
|
||||
let entity = DrawingStickerEntity(content: .file(.standalone(media: file), .sticker))
|
||||
if explicitlyStaticStickers.contains(file.fileId.id) {
|
||||
entity.isExplicitlyStatic = true
|
||||
}
|
||||
entity.referenceDrawingSize = CGSize(width: itemSize * 4.0, height: itemSize * 4.0)
|
||||
entity.scale = scale
|
||||
entity.position = textPosition.offsetBy(
|
||||
|
@ -477,16 +477,18 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
|
||||
}
|
||||
|
||||
func lcm(_ x: Int64, _ y: Int64) -> Int64 {
|
||||
let x = max(x, 1)
|
||||
let y = max(y, 1)
|
||||
return x / gcd(x, y) * y
|
||||
}
|
||||
|
||||
|
||||
return combineLatest(durations)
|
||||
|> map { durations in
|
||||
var result: Double
|
||||
let minDuration: Double = 3.0
|
||||
if durations.count > 1 {
|
||||
let reduced = durations.reduce(1.0) { lhs, rhs -> Double in
|
||||
return Double(lcm(Int64(lhs * 10.0), Int64(rhs * 10.0)))
|
||||
return Double(lcm(Int64(lhs * 100.0), Int64(rhs * 100.0)))
|
||||
}
|
||||
result = min(6.0, Double(reduced) / 10.0)
|
||||
} else if let duration = durations.first {
|
||||
|
@ -33,12 +33,15 @@ private func currentTemperatureUnit() -> TemperatureUnit {
|
||||
return temperatureUnit
|
||||
}
|
||||
|
||||
public func stringForTemperature(_ value: Double) -> String {
|
||||
private var formatter: MeasurementFormatter = {
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.unitStyle = .short
|
||||
formatter.numberFormatter.maximumFractionDigits = 0
|
||||
formatter.unitOptions = .temperatureWithoutUnit
|
||||
return formatter
|
||||
}()
|
||||
|
||||
public func stringForTemperature(_ value: Double) -> String {
|
||||
let valueString = formatter.string(from: Measurement(value: value, unit: UnitTemperature.celsius)).trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,.").inverted)
|
||||
return valueString + currentTemperatureUnit().suffix
|
||||
}
|
||||
|
@ -776,7 +776,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
}
|
||||
|
||||
public final class EmojiTextAttachmentView: UIView {
|
||||
private let contentLayer: InlineStickerItemLayer
|
||||
public let contentLayer: InlineStickerItemLayer
|
||||
|
||||
public var isActive: Bool = true {
|
||||
didSet {
|
||||
@ -826,7 +826,7 @@ public final class EmojiTextAttachmentView: UIView {
|
||||
public final class CustomEmojiContainerView: UIView {
|
||||
private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView?
|
||||
|
||||
private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
|
||||
public private(set) var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
|
||||
|
||||
public init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) {
|
||||
self.emojiViewProvider = emojiViewProvider
|
||||
|
@ -88,11 +88,15 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
|
||||
return true
|
||||
}
|
||||
var isAnimated = false
|
||||
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
|
||||
if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
|
||||
isAnimated = true
|
||||
|
||||
if let renderSubEntities = self.renderSubEntities {
|
||||
for entity in renderSubEntities {
|
||||
if entity.isAnimated {
|
||||
isAnimated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return isAnimated
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import UIKitRuntimeUtils
|
||||
|
||||
private let minimizedNavigationHeight: CGFloat = 44.0
|
||||
private let minimizedTopMargin: CGFloat = 3.0
|
||||
private let maximizeLastStandingController = false
|
||||
|
||||
final class ScrollViewImpl: UIScrollView {
|
||||
var shouldPassthrough: () -> Bool = { return false }
|
||||
@ -112,7 +113,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
self.snapshotContainerView.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.cornerRadius = 10.0
|
||||
applySmoothRoundedCorners(self.layer)
|
||||
@ -308,7 +309,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
public private(set) var isExpanded: Bool = false
|
||||
public var willMaximize: (() -> Void)?
|
||||
public var willMaximize: ((MinimizedContainer) -> Void)?
|
||||
public var willDismiss: ((MinimizedContainer) -> Void)?
|
||||
public var didDismiss: ((MinimizedContainer) -> Void)?
|
||||
|
||||
public private(set) var statusBarStyle: StatusBarStyle = .White
|
||||
public var statusBarStyleUpdated: (() -> Void)?
|
||||
@ -500,10 +503,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
self.currentTransition = .dismiss(itemId: itemId)
|
||||
|
||||
self.items.removeAll(where: { $0.id == itemId })
|
||||
if self.items.count == 1 {
|
||||
if self.items.count == 1, maximizeLastStandingController {
|
||||
self.isExpanded = false
|
||||
self.willMaximize?()
|
||||
self.willMaximize?(self)
|
||||
needsLayout = false
|
||||
} else if self.items.count == 0 {
|
||||
self.willDismiss?(self)
|
||||
self.isExpanded = false
|
||||
}
|
||||
}
|
||||
if let item = self.items.first(where: { $0.id == itemId }), !item.controller.shouldDismissImmediately() {
|
||||
@ -558,6 +564,15 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
self.requestUpdate(transition: transition)
|
||||
}
|
||||
|
||||
public func removeController(_ viewController: MinimizableController) {
|
||||
guard let item = self.items.first(where: { $0.controller === viewController }) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.items.removeAll(where: { $0.id == item.id })
|
||||
self.requestUpdate(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
|
||||
private enum Transition: Equatable {
|
||||
case minimize(itemId: AnyHashable)
|
||||
case maximize(itemId: AnyHashable)
|
||||
@ -759,7 +774,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
if let currentTransition = self.currentTransition {
|
||||
if currentTransition.matches(item: item) {
|
||||
continue
|
||||
} else if case .dismiss = currentTransition, self.items.count == 1 {
|
||||
} else if case .dismiss = currentTransition, self.items.count == 1 && maximizeLastStandingController {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -789,10 +804,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
self.currentTransition = .dismiss(itemId: item.id)
|
||||
|
||||
self.items.removeAll(where: { $0.id == item.id })
|
||||
if self.items.count == 1 {
|
||||
if self.items.count == 1, maximizeLastStandingController {
|
||||
self.isExpanded = false
|
||||
self.willMaximize?()
|
||||
self.willMaximize?(self)
|
||||
needsLayout = false
|
||||
} else if self.items.count == 0 {
|
||||
self.isExpanded = false
|
||||
self.willDismiss?(self)
|
||||
}
|
||||
if needsLayout {
|
||||
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||
@ -1097,7 +1115,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
guard let dismissedItemNode = self.itemNodes[itemId] else {
|
||||
return
|
||||
}
|
||||
if self.items.count == 1 {
|
||||
if self.items.count == 1, maximizeLastStandingController {
|
||||
if let itemNode = self.itemNodes.first(where: { $0.0 != itemId })?.value, let navigationController = self.navigationController {
|
||||
itemNode.item.beforeMaximize(navigationController, { [weak self] in
|
||||
guard let self else {
|
||||
@ -1137,6 +1155,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
}
|
||||
transition.updatePosition(node: dismissedItemNode, position: CGPoint(x: -layout.size.width, y: dismissedItemNode.position.y))
|
||||
} else {
|
||||
let isLast = self.items.isEmpty
|
||||
transition.updatePosition(node: dismissedItemNode, position: CGPoint(x: -layout.size.width, y: dismissedItemNode.position.y), completion: { _ in
|
||||
self.isApplyingTransition = false
|
||||
if self.currentTransition == currentTransition {
|
||||
@ -1146,7 +1165,15 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
||||
|
||||
self.itemNodes[itemId] = nil
|
||||
dismissedItemNode.removeFromSupernode()
|
||||
|
||||
if isLast {
|
||||
self.didDismiss?(self)
|
||||
}
|
||||
})
|
||||
if isLast {
|
||||
let dismissOffset = collapsedHeight(layout: layout)
|
||||
transition.updatePosition(layer: self.bottomEdgeView.layer, position: self.bottomEdgeView.layer.position.offsetBy(dx: 0.0, dy: dismissOffset))
|
||||
}
|
||||
}
|
||||
case .dismissAll:
|
||||
let dismissOffset = collapsedHeight(layout: layout)
|
||||
|
@ -73,7 +73,7 @@ final class MinimizedHeaderNode: ASDisplayNode {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let titles = titles.compactMap { $0 }
|
||||
let titles = titles.compactMap { $0 }.filter { !$0.isEmpty }
|
||||
if titles.count == 1, let title = titles.first {
|
||||
self.title = title
|
||||
} else if let title = titles.last {
|
||||
|
@ -113,7 +113,10 @@ func interitemSpacing(itemCount: Int, boundingSize: CGSize, insets: UIEdgeInsets
|
||||
|
||||
func frameForIndex(index: Int, size: CGSize, insets: UIEdgeInsets, itemCount: Int, boundingSize: CGSize) -> CGRect {
|
||||
let spacing = interitemSpacing(itemCount: itemCount, boundingSize: boundingSize, insets: insets)
|
||||
let y = additionalInsetTop + insets.top + spacing * CGFloat(index)
|
||||
var y = additionalInsetTop + insets.top + spacing * CGFloat(index)
|
||||
if itemCount == 1 {
|
||||
y += 72.0
|
||||
}
|
||||
let origin = CGPoint(x: insets.left, y: y)
|
||||
|
||||
return CGRect(origin: origin, size: CGSize(width: size.width - insets.left - insets.right, height: size.height))
|
||||
|
@ -17,6 +17,7 @@ private var nextRenderTargetId: Int64 = 1
|
||||
|
||||
open class MultiAnimationRenderTarget: SimpleLayer {
|
||||
public let id: Int64
|
||||
public var numFrames: Int?
|
||||
|
||||
let deinitCallbacks = Bag<() -> Void>()
|
||||
let updateStateCallbacks = Bag<() -> Void>()
|
||||
@ -545,6 +546,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
|
||||
target.contents = loadedFrame.image.cgImage
|
||||
target.numFrames = item.numFrames
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = loadedFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
@ -580,6 +582,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
completion(false, true)
|
||||
return
|
||||
}
|
||||
target.numFrames = item.numFrames
|
||||
if let loadedFrame = loadedFrame {
|
||||
if let cgImage = loadedFrame.image.cgImage {
|
||||
if hadIntermediateUpdate {
|
||||
|
@ -336,7 +336,7 @@ public final class StarsLabelComponent: CombinedComponent {
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let iconSize = CGSize(width: 20.0, height: 20.0)
|
||||
let iconSize = CGSize(width: 24.0, height: 24.0)
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Premium/Stars/StarLarge",
|
||||
|
@ -699,7 +699,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
originY += description.size.height + 10.0
|
||||
}
|
||||
|
||||
let amountSpacing: CGFloat = 3.0
|
||||
let amountSpacing: CGFloat = 1.0
|
||||
var totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width
|
||||
var amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0
|
||||
if isRefund {
|
||||
@ -746,8 +746,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
context.add(amountStar
|
||||
.position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0, y: amountOrigin + amountStar.size.height / 2.0))
|
||||
)
|
||||
|
||||
|
||||
|
||||
context.add(table
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
|
||||
)
|
||||
|
@ -161,7 +161,7 @@ final class StarsBalanceComponent: Component {
|
||||
let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: contentHeight - 3.0), size: titleSize)
|
||||
titleView.frame = titleFrame
|
||||
|
||||
self.icon.frame = CGRect(origin: CGPoint(x: origin, y: contentHeight + 2.0), size: icon.size)
|
||||
self.icon.frame = CGRect(origin: CGPoint(x: origin, y: contentHeight), size: icon.size)
|
||||
}
|
||||
}
|
||||
contentHeight += titleSize.height
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "balancestar_48 (2).pdf",
|
||||
"filename" : "StarBalance.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/StarBalance.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/BalanceStar.imageset/StarBalance.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Star20 (3).pdf",
|
||||
"filename" : "StarLarge.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/StarLarge.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/StarLarge.pdf
vendored
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star_18 (3).pdf",
|
||||
"filename" : "StarMedium.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/StarMedium.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMedium.imageset/StarMedium.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star_16 (3).pdf",
|
||||
"filename" : "StarSmall.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/StarSmall.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarSmall.imageset/StarSmall.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user