diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 655fdaa421..2d66c0b2f6 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -30,6 +30,7 @@ swift_library( "//submodules/ContextUI:ContextUI", "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/PhotoResources:PhotoResources", + "//submodules/MediaResources:MediaResources", "//submodules/SemanticStatusNode:SemanticStatusNode", "//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent", ], diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 117b8fe25b..42dceef3c2 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -161,7 +161,8 @@ public class AttachmentController: ViewController { private let initialButton: AttachmentButtonType private let fromMenu: Bool - public var dismissed: () -> Void = {} + public var willDismiss: () -> Void = {} + public var didDismiss: () -> Void = {} public var mediaPickerContext: AttachmentMediaPickerContext? { get { @@ -268,6 +269,7 @@ public class AttachmentController: ViewController { self.container.canHaveKeyboardFocus = true self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, updatedPresentationData: controller.updatedPresentationData) self.panel.fromMenu = controller.fromMenu + self.panel.isStandalone = controller.isStandalone super.init() @@ -636,7 +638,10 @@ public class AttachmentController: ViewController { var containerLayout = layout let containerRect: CGRect + var isCompact = true if case .regular = layout.metrics.widthClass { + isCompact = false + let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0 let size = CGSize(width: 390.0, height: min(620.0, availableHeight)) @@ -645,17 +650,28 @@ public class AttachmentController: ViewController { let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let position: CGPoint = CGPoint(x: masterWidth - 174.0, y: layout.size.height - size.height - insets.bottom - 40.0) - containerRect = CGRect(origin: position, size: size) + if controller.isStandalone { + var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0) + if let inputHeight = layout.inputHeight, inputHeight > 88.0 { + containerY = layout.size.height - inputHeight - size.height - 80.0 + } + containerRect = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: containerY), size: size) + } else { + containerRect = CGRect(origin: position, size: size) + } containerLayout.size = containerRect.size containerLayout.intrinsicInsets.bottom = 12.0 containerLayout.inputHeight = nil - if self.wrapperNode.view.mask == nil { + if controller.isStandalone { + self.wrapperNode.cornerRadius = 10.0 + } else if self.wrapperNode.view.mask == nil { let maskView = UIImageView() maskView.image = generateMaskImage() maskView.contentMode = .scaleToFill self.wrapperNode.view.mask = maskView } + if let maskView = self.wrapperNode.view.mask { transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size)) } @@ -697,7 +713,7 @@ public class AttachmentController: ViewController { if fromMenu && !hasButton, let inputContainerHeight = self.inputContainerHeight { panelHeight = inputContainerHeight } - if hasPanel || hasButton || fromMenu { + if hasPanel || hasButton || (fromMenu && isCompact) { containerInsets.bottom = panelHeight } @@ -716,13 +732,13 @@ public class AttachmentController: ViewController { panelTransition = .animated(duration: 0.25, curve: .easeInOut) } var panelY = containerRect.height - panelHeight - if fromMenu { + if fromMenu && isCompact { panelY = layout.size.height - panelHeight } else if !hasPanel && !hasButton { panelY = containerRect.height } - if fromMenu { + if fromMenu && isCompact { if hasButton { self.panel.isHidden = false self.inputContainerNode?.isHidden = true @@ -735,7 +751,7 @@ public class AttachmentController: ViewController { } panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight)), completion: { [weak self] finished in - if transitioning && finished { + if transitioning && finished, isCompact { self?.panel.isHidden = !hasButton self?.inputContainerNode?.isHidden = hasButton } @@ -765,7 +781,8 @@ public class AttachmentController: ViewController { if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { self.wrapperNode.addSubnode(self.container) - if controller.fromMenu { + + if fromMenu, let _ = controller.getInputContainerNode() { self.addSubnode(self.panel) } else { self.container.addSubnode(self.panel) @@ -814,6 +831,10 @@ public class AttachmentController: ViewController { fatalError("init(coder:) has not been implemented") } + fileprivate var isStandalone: Bool { + return self.buttons.contains(.standalone) + } + private var node: Node { return self.displayNode as! Node } @@ -823,24 +844,29 @@ public class AttachmentController: ViewController { self.displayNodeDidLoad() } - private var didDismiss = false + private var dismissedFlag = false public func _dismiss() { - self.dismissed() super.dismiss(animated: false, completion: {}) } + public var ensureUnfocused = true + public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - self.view.endEditing(true) + if self.ensureUnfocused { + self.view.endEditing(true) + } if flag { - if !self.didDismiss { - self.didDismiss = true - self.dismissed() + if !self.dismissedFlag { + self.dismissedFlag = true + self.willDismiss() self.node.animateOut(completion: { [weak self] in + self?.didDismiss() self?._dismiss() completion?() }) } } else { + self.didDismiss() self._dismiss() completion?() } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index c05e10b035..0fdbcf5f5c 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -15,6 +15,7 @@ import ChatTextLinkEditUI import PhotoResources import AnimatedStickerComponent import SemanticStatusNode +import MediaResources private let buttonSize = CGSize(width: 88.0, height: 49.0) private let smallButtonWidth: CGFloat = 69.0 @@ -79,7 +80,7 @@ private final class IconComponent: Component { self.image = nil } - self.disposable = (svgIconImageFile(account: component.account, fileReference: fileReference, fetched: true) + self.disposable = (svgIconImageFile(account: component.account, fileReference: fileReference) |> runOn(Queue.concurrentDefaultQueue()) |> deliverOnMainQueue).start(next: { [weak self] transform in let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: availableSize, boundingSize: availableSize, intrinsicInsets: UIEdgeInsets()) @@ -221,9 +222,9 @@ private final class AttachButtonComponent: CombinedComponent { animation: AnimatedStickerComponent.Animation( source: .file(media: animationFile), scale: UIScreenScale, - loop: false, - tintColor: tintColor + loop: false ), + tintColor: tintColor, isAnimating: component.isSelected, size: CGSize(width: iconSize.width, height: iconSize.height) ), @@ -361,23 +362,26 @@ public struct AttachmentMainButtonState { let textColor: UIColor let isVisible: Bool let isLoading: Bool + let isEnabled: Bool public init( text: String?, backgroundColor: UIColor, textColor: UIColor, isVisible: Bool, - isLoading: Bool + isLoading: Bool, + isEnabled: Bool ) { self.text = text self.backgroundColor = backgroundColor self.textColor = textColor self.isVisible = isVisible self.isLoading = isLoading + self.isEnabled = isEnabled } static var initial: AttachmentMainButtonState { - return AttachmentMainButtonState(text: nil, backgroundColor: .clear, textColor: .clear, isVisible: false, isLoading: false) + return AttachmentMainButtonState(text: nil, backgroundColor: .clear, textColor: .clear, isVisible: false, isLoading: false, isEnabled: false) } } @@ -401,7 +405,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.addSubnode(self.statusNode) self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { + if let strongSelf = self, strongSelf.state.isEnabled { if highlighted { strongSelf.layer.removeAnimation(forKey: "opacity") strongSelf.alpha = 0.65 @@ -479,6 +483,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var scrollLayout: (width: CGFloat, contentSize: CGSize)? var fromMenu: Bool = false + var isStandalone: Bool = false var selectionChanged: (AttachmentButtonType) -> Bool = { _ in return false } var beganTextEditing: () -> Void = {} @@ -775,10 +780,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var leftNodeOriginX = (layout.size.width - internalWidth) / 2.0 var buttonWidth = buttonSize.width - if self.buttons.count > 6 { + if self.buttons.count > 6 && layout.size.width < layout.size.height { buttonWidth = smallButtonWidth distanceBetweenNodes = buttonWidth - leftNodeOriginX = sideInset + buttonWidth / 2.0 + leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 } for i in 0 ..< self.buttons.count { @@ -803,9 +808,29 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { let type = self.buttons[i] if case let .app(peer, _, iconFiles) = type { for (name, file) in iconFiles { - if [.default, .iOSAnimated].contains(name) { + if [.default, .iOSAnimated, .placeholder].contains(name) { if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(peer) { - self.iconDisposables[file.fileId] = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .attachBot(peer: peer, media: file)).start() + if case .placeholder = name { + let account = self.context.account + let path = account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) + if !FileManager.default.fileExists(atPath: path) { + let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + let accountResource = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) + + let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .media(media: .attachBot(peer: peer, media: file), resource: file.resource)) + let fetchedFullSizeDisposable = fetchedFullSize.start() + let fullSizeDisposable = accountResource.start() + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + self.iconDisposables[file.fileId] = accountFullSizeData.start() + } + } else { + self.iconDisposables[file.fileId] = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .attachBot(peer: peer, media: file)).start() + } } } } @@ -848,9 +873,9 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var contentSize = CGSize(width: layout.size.width, height: buttonSize.height) var buttonWidth = buttonSize.width - if self.buttons.count > 6 { + if self.buttons.count > 6 && layout.size.width < layout.size.height { buttonWidth = smallButtonWidth - contentSize = CGSize(width: sideInset * 2.0 + CGFloat(self.buttons.count) * buttonWidth, height: buttonSize.height) + contentSize.width = layout.safeInsets.left + layout.safeInsets.right + sideInset * 2.0 + CGFloat(self.buttons.count) * buttonWidth } self.scrollLayout = (layout.size.width, contentSize) @@ -899,7 +924,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { func updateMainButtonState(_ mainButtonState: AttachmentMainButtonState?) { var currentButtonState = self.mainButtonState if mainButtonState == nil { - currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, backgroundColor: currentButtonState.backgroundColor, textColor: currentButtonState.textColor, isVisible: false, isLoading: false) + currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, backgroundColor: currentButtonState.backgroundColor, textColor: currentButtonState.textColor, isVisible: false, isLoading: false, isEnabled: currentButtonState.isEnabled) } self.mainButtonState = mainButtonState ?? currentButtonState } @@ -955,9 +980,9 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.view.addSubview(inputNodeSnapshotView) let targetInputPosition = CGPoint(x: inputNodeSnapshotView.center.x + inputNodeSnapshotView.frame.width, y: self.mainButtonNode.position.y) - transition.updatePosition(layer: inputNodeSnapshotView.layer, position: targetInputPosition, completion: { [weak inputNodeSnapshotView] _ in + transition.updatePosition(layer: inputNodeSnapshotView.layer, position: targetInputPosition, completion: { [weak inputNodeSnapshotView, weak self] _ in inputNodeSnapshotView?.removeFromSuperview() - self.animatingTransition = false + self?.animatingTransition = false }) } @@ -974,7 +999,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.dismissed = dismissed let action = { - guard let menuIconSnapshotView = inputTransition.menuIconNode.view.snapshotView(afterScreenUpdates: true), let menuTextSnapshotView = inputTransition.menuTextNode.view.snapshotView(afterScreenUpdates: false) else { + guard let menuIconSnapshotView = inputTransition.menuIconNode.view.snapshotView(afterScreenUpdates: false), let menuTextSnapshotView = inputTransition.menuTextNode.view.snapshotView(afterScreenUpdates: false) else { return } @@ -1011,19 +1036,19 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { let targetInputFrame = CGRect(x: inputTransition.menuButtonNode.frame.maxX, y: 0.0, width: inputNodeSnapshotView.frame.width - inputTransition.menuButtonNode.frame.maxX, height: inputNodeSnapshotView.frame.height) inputNodeSnapshotView.frame = targetInputFrame.offsetBy(dx: targetInputFrame.width, dy: self.mainButtonNode.position.y - inputNodeSnapshotView.frame.height / 2.0) self.view.addSubview(inputNodeSnapshotView) - transition.updateFrame(layer: inputNodeSnapshotView.layer, frame: targetInputFrame, completion: { [weak inputNodeSnapshotView, weak menuIconSnapshotView, weak menuTextSnapshotView] _ in + transition.updateFrame(layer: inputNodeSnapshotView.layer, frame: targetInputFrame, completion: { [weak inputNodeSnapshotView, weak menuIconSnapshotView, weak menuTextSnapshotView, weak self] _ in inputNodeSnapshotView?.removeFromSuperview() - self.animatingTransition = false + self?.animatingTransition = false if !dismissed { menuIconSnapshotView?.removeFromSuperview() menuTextSnapshotView?.removeFromSuperview() - self.mainButtonNode.backgroundColor = sourceButtonColor - self.mainButtonNode.frame = sourceButtonFrame - self.mainButtonNode.textNode.position = sourceButtonTextPosition - self.mainButtonNode.textNode.layer.removeAllAnimations() - self.mainButtonNode.cornerRadius = sourceButtonCornerRadius + self?.mainButtonNode.backgroundColor = sourceButtonColor + self?.mainButtonNode.frame = sourceButtonFrame + self?.mainButtonNode.textNode.position = sourceButtonTextPosition + self?.mainButtonNode.textNode.layer.removeAllAnimations() + self?.mainButtonNode.cornerRadius = sourceButtonCornerRadius } }) } @@ -1087,9 +1112,16 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { let containerTransition: ContainedViewLayoutTransition let containerFrame: CGRect if isButtonVisible { - let height: CGFloat + var height: CGFloat if layout.intrinsicInsets.bottom > 0.0 && (layout.inputHeight ?? 0.0).isZero { height = bounds.height + 9.0 + if case .regular = layout.metrics.widthClass { + if self.isStandalone { + height -= 3.0 + } else { + height += 6.0 + } + } } else { height = bounds.height + 9.0 + 8.0 } diff --git a/submodules/Components/AnimatedStickerComponent/Sources/AnimatedStickerComponent.swift b/submodules/Components/AnimatedStickerComponent/Sources/AnimatedStickerComponent.swift index 2207c317cd..1d46928659 100644 --- a/submodules/Components/AnimatedStickerComponent/Sources/AnimatedStickerComponent.swift +++ b/submodules/Components/AnimatedStickerComponent/Sources/AnimatedStickerComponent.swift @@ -16,24 +16,24 @@ public final class AnimatedStickerComponent: Component { public var source: Source public var scale: CGFloat public var loop: Bool - public var tintColor: UIColor? - public init(source: Source, scale: CGFloat = 2.0, loop: Bool, tintColor: UIColor? = nil) { + public init(source: Source, scale: CGFloat = 2.0, loop: Bool) { self.source = source self.scale = scale self.loop = loop - self.tintColor = tintColor } } public let account: Account public let animation: Animation + public var tintColor: UIColor? public let isAnimating: Bool public let size: CGSize - public init(account: Account, animation: Animation, isAnimating: Bool = true, size: CGSize) { + public init(account: Account, animation: Animation, tintColor: UIColor? = nil, isAnimating: Bool = true, size: CGSize) { self.account = account self.animation = animation + self.tintColor = tintColor self.isAnimating = isAnimating self.size = size } @@ -45,6 +45,9 @@ public final class AnimatedStickerComponent: Component { if lhs.animation != rhs.animation { return false } + if lhs.tintColor != rhs.tintColor { + return false + } if lhs.isAnimating != rhs.isAnimating { return false } @@ -100,13 +103,14 @@ public final class AnimatedStickerComponent: Component { case let .file(media): source = AnimatedStickerResourceSource(account: component.account, resource: media.resource, fitzModifier: nil, isVideo: false) } - animationNode.setOverlayColor(component.animation.tintColor, replace: true, animated: false) var playbackMode: AnimatedStickerPlaybackMode = .still(.start) if component.animation.loop { playbackMode = .loop } else if component.isAnimating { playbackMode = .once + } else { + animationNode.autoplay = true } animationNode.setup(source: source, width: Int(component.size.width * component.animation.scale), height: Int(component.size.height * component.animation.scale), playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil)) animationNode.visibility = self.isInHierarchy @@ -115,6 +119,8 @@ public final class AnimatedStickerComponent: Component { self.addSubnode(animationNode) } + self.animationNode?.setOverlayColor(component.tintColor, replace: true, animated: false) + if !component.animation.loop && component.isAnimating != self.component?.isAnimating { if component.isAnimating { let _ = self.animationNode?.playIfNeeded() diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 2b9d504e72..49a5be128b 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -714,7 +714,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { override init() { self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false) self.parentShadowNode = ASImageNode() - self.parentShadowNode.image = UIImage(bundleImageName: "Components/Context Menu/Shadow")?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60) + self.parentShadowNode.image = UIImage(bundleImageName: "Components/Context Menu/Shadow")?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 48) super.init() diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 9636006ae3..6e73ee816e 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -14,6 +14,7 @@ import OverlayStatusController import AccountContext import AppBundle import ZipArchive +import WebKit @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { @@ -74,6 +75,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case resetHoles(PresentationTheme) case reindexUnread(PresentationTheme) case resetBiometricsData(PresentationTheme) + case resetWebViewCache(PresentationTheme) case optimizeDatabase(PresentationTheme) case photoPreview(PresentationTheme, Bool) case knockoutWallpaper(PresentationTheme, Bool) @@ -103,7 +105,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .snow: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .snow: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -160,30 +162,32 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 20 case .resetBiometricsData: return 21 - case .optimizeDatabase: + case .resetWebViewCache: return 22 - case .photoPreview: + case .optimizeDatabase: return 23 - case .knockoutWallpaper: + case .photoPreview: return 24 + case .knockoutWallpaper: + return 25 case .experimentalCompatibility: return 26 case .enableDebugDataDisplay: return 27 case .acceleratedStickers: - return 29 + return 28 case .experimentalBackground: - return 30 + return 29 case .snow: - return 31 + return 30 case .playerEmbedding: - return 32 + return 31 case .playlistPlayback: - return 33 + return 32 case .voiceConference: - return 34 + return 33 case let .preferredVideoCodec(index, _, _, _): - return 35 + index + return 34 + index case .disableVideoAspectScaling: return 100 case .enableVoipTcp: @@ -777,6 +781,10 @@ private enum DebugControllerEntry: ItemListNodeEntry { return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil) }).start() }) + case .resetWebViewCache: + return ItemListActionItem(presentationData: presentationData, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ }) + }) case .optimizeDatabase: return ItemListActionItem(presentationData: presentationData, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { @@ -968,6 +976,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.resetHoles(presentationData.theme)) if isMainApp { entries.append(.reindexUnread(presentationData.theme)) + entries.append(.resetWebViewCache(presentationData.theme)) } entries.append(.optimizeDatabase(presentationData.theme)) if isMainApp { diff --git a/submodules/MediaResources/Sources/CachedResourceRepresentations.swift b/submodules/MediaResources/Sources/CachedResourceRepresentations.swift index 6b92e55531..bb6baa9a89 100644 --- a/submodules/MediaResources/Sources/CachedResourceRepresentations.swift +++ b/submodules/MediaResources/Sources/CachedResourceRepresentations.swift @@ -344,3 +344,23 @@ public final class CachedPreparedPatternWallpaperRepresentation: CachedMediaReso } } } + + +public final class CachedPreparedSvgRepresentation: CachedMediaResourceRepresentation { + public let keepDuration: CachedMediaRepresentationKeepDuration = .general + + public var uniqueId: String { + return "prepared-svg" + } + + public init() { + } + + public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if to is CachedPreparedSvgRepresentation { + return true + } else { + return false + } + } +} diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index f9231de7af..b4438bcc84 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2283,28 +2283,35 @@ public func instantPageImageFile(account: Account, fileReference: FileMediaRefer } } -public func svgIconImageFile(account: Account, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false, fetched: false) +public func svgIconImageFile(account: Account, fileReference: FileMediaReference, stickToTop: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let data = account.postbox.mediaBox.cachedResourceRepresentation(fileReference.media.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) + + return data |> map { value in - let fullSizePath = value._1 - let fullSizeComplete = value._2 + let fullSizePath = value.path + let fullSizeComplete = value.complete return { arguments in -// assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + var fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) var fullSizeImage: UIImage? let imageOrientation: UIImage.Orientation = .up - if let fullSizePath = fullSizePath { - if fullSizeComplete, let data = try? Data(contentsOf: URL(fileURLWithPath: fullSizePath)) { - fullSizeImage = drawSvgImage(data, CGSize(width: 90.0, height: 90.0), .clear, .black, false) + if fullSizeComplete, let data = try? Data(contentsOf: URL(fileURLWithPath: fullSizePath)) { + fullSizeImage = renderPreparedImage(data, CGSize.zero, .clear, UIScreenScale) + +// fullSizeImage = drawSvgImage(data, stickToTop ? CGSize.zero : CGSize(width: 90.0, height: 90.0), .clear, .black, false) + if let image = fullSizeImage { + fittedSize = image.size.aspectFitted(arguments.boundingSize) } } - let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) + var fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) + if stickToTop { + fittedRect.origin.y = drawingRect.size.height - fittedSize.height + } context.withFlippedContext { c in if let fullSizeImage = fullSizeImage?.cgImage { diff --git a/submodules/Svg/PublicHeaders/Svg/Svg.h b/submodules/Svg/PublicHeaders/Svg/Svg.h index 48e51ef4ff..0c05521c13 100755 --- a/submodules/Svg/PublicHeaders/Svg/Svg.h +++ b/submodules/Svg/PublicHeaders/Svg/Svg.h @@ -5,7 +5,7 @@ #import NSData * _Nullable prepareSvgImage(NSData * _Nonnull data); -UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size); +UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale); UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, bool opaque); diff --git a/submodules/Svg/Sources/Svg.m b/submodules/Svg/Sources/Svg.m index df48b16ee9..06835a760d 100755 --- a/submodules/Svg/Sources/Svg.m +++ b/submodules/Svg/Sources/Svg.m @@ -111,6 +111,10 @@ UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *b return nil; } + if (CGSizeEqualToSize(size, CGSizeZero)) { + size = CGSizeMake(image->width, image->height); + } + double deltaTime = -1.0f * [startTime timeIntervalSinceNow]; printf("parseTime = %f\n", deltaTime); @@ -354,11 +358,11 @@ UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *b @end -UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size) { +UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, CGFloat scale) { NSDate *startTime = [NSDate date]; UIColor *foregroundColor = [UIColor whiteColor]; - UIColor *backgroundColor = [UIColor blackColor]; + int32_t ptr = 0; int32_t width; @@ -373,17 +377,27 @@ UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size) { [data getBytes:&height range:NSMakeRange(ptr, sizeof(height))]; ptr += sizeof(height); - UIGraphicsBeginImageContextWithOptions(size, true, 1.0); + if (CGSizeEqualToSize(size, CGSizeZero)) { + size = CGSizeMake(width, height); + } + + bool isTransparent = [backgroundColor isEqual:[UIColor clearColor]]; + + UIGraphicsBeginImageContextWithOptions(size, !isTransparent, scale); CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); - CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height)); + if (isTransparent) { + CGContextClearRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height)); + } else { + CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height)); + } CGSize svgSize = CGSizeMake(width, height); CGSize drawingSize = aspectFillSize(svgSize, size); - CGFloat scale = MAX(size.width / MAX(1.0, svgSize.width), size.height / MAX(1.0, svgSize.height)); + CGFloat renderScale = MAX(size.width / MAX(1.0, svgSize.width), size.height / MAX(1.0, svgSize.height)); - CGContextScaleCTM(context, scale, scale); + CGContextScaleCTM(context, renderScale, renderScale); CGContextTranslateCTM(context, (size.width - drawingSize.width) / 2.0, (size.height - drawingSize.height) / 2.0); while (ptr < data.length) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index 7980e44625..17cbfdb114 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -16,6 +16,7 @@ public final class AttachMenuBots: Equatable, Codable { case iOSStatic case iOSAnimated case macOSAnimated + case placeholder init?(string: String) { switch string { @@ -27,6 +28,8 @@ public final class AttachMenuBots: Equatable, Codable { self = .iOSAnimated case "macos_animated": self = .macOSAnimated + case "placeholder_static": + self = .placeholder default: return nil } diff --git a/submodules/TelegramUI/Images.xcassets/Components/Context Menu/Shadow.imageset/Shadow.png b/submodules/TelegramUI/Images.xcassets/Components/Context Menu/Shadow.imageset/Shadow.png index e050a86db7..94b4bd34f8 100644 Binary files a/submodules/TelegramUI/Images.xcassets/Components/Context Menu/Shadow.imageset/Shadow.png and b/submodules/TelegramUI/Images.xcassets/Components/Context Menu/Shadow.imageset/Shadow.png differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b29bac09b9..523b0df863 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -279,6 +279,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var bankCardDisposable: MetaDisposable? private var hasActiveGroupCallDisposable: Disposable? private var sendAsPeersDisposable: Disposable? + private let preloadAttachBotIconsDisposables = DisposableSet() private let editingMessage = ValuePromise(nil, ignoreRepeated: true) private let startingBot = ValuePromise(false, ignoreRepeated: true) @@ -2931,6 +2932,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .speak: let _ = speakText(text.string) case .translate: + strongSelf.chatDisplayNode.dismissInput() + let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) |> take(1) |> deliverOnMainQueue).start(next: { sharedData in @@ -3360,11 +3363,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let openWebView = { if fromMenu { - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: true) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: true) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, getInputContainerNode: { [weak self] in - if let strongSelf = self { + if let strongSelf = self, let layout = strongSelf.validLayout, case .compact = layout.metrics.widthClass { return (strongSelf.chatDisplayNode.getWindowInputAccessoryHeight(), strongSelf.chatDisplayNode.inputPanelContainerNode, { return strongSelf.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil) }) @@ -3373,11 +3376,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, completion: { [weak self] in self?.chatDisplayNode.historyNode.scrollToEndOfHistory() - }, dismissed: { [weak self] in + }, willDismiss: { [weak self] in self?.interfaceInteraction?.updateShowWebView { _ in return false } - strongSelf.chatDisplayNode.insertSubnode(strongSelf.chatDisplayNode.inputPanelContainerNode, aboveSubnode: strongSelf.chatDisplayNode.historyNodeContainer) + }, didDismiss: { [weak self] in + if let strongSelf = self { + let isFocused = strongSelf.chatDisplayNode.textInputPanelNode?.isFocused ?? false + strongSelf.chatDisplayNode.insertSubnode(strongSelf.chatDisplayNode.inputPanelContainerNode, aboveSubnode: strongSelf.chatDisplayNode.historyNodeContainer) + if isFocused { + strongSelf.chatDisplayNode.textInputPanelNode?.ensureFocused() + } + } }) strongSelf.present(controller, in: .window(.root)) strongSelf.currentMenuWebAppController = controller @@ -3390,7 +3400,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }) @@ -3410,7 +3420,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, fromMenu: false) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, fromMenu: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, completion: { [weak self] in @@ -4822,6 +4832,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.nextChannelToReadDisposable?.dispose() self.inviteRequestsDisposable.dispose() self.sendAsPeersDisposable?.dispose() + self.preloadAttachBotIconsDisposables.dispose() } public func updatePresentationMode(_ mode: ChatControllerPresentationMode) { @@ -6784,7 +6795,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return value }) }) - if updatedInputMode == .text { + var dismissWebView = false + switch updatedInputMode { + case .text, .media, .inputButtons: + dismissWebView = true + default: + break + } + if dismissWebView { updated = updated.updatedShowWebView(false) } return updated @@ -6794,9 +6812,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - strongSelf.interfaceInteraction?.updateShowWebView { _ in - return false - } strongSelf.chatDisplayNode.openStickers() strongSelf.mediaRecordingModeTooltipController?.dismissImmediately() }, editMessage: { [weak self] in @@ -9047,6 +9062,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }).start()) } } + + self.preloadAttachBotIcons() } if let _ = self.focusOnSearchAfterAppearance { @@ -9772,6 +9789,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let currentMenuWebAppController = self.currentMenuWebAppController, !self.presentationInterfaceState.showWebView { self.currentMenuWebAppController = nil + if let currentMenuWebAppController = currentMenuWebAppController as? AttachmentController { + currentMenuWebAppController.ensureUnfocused = false + } currentMenuWebAppController.dismiss(animated: true, completion: nil) } @@ -10941,7 +10961,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G completion(controller, nil) strongSelf.controllerNavigationDisposable.set(nil) case let .app(bot, botName, _): - let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, buttonText: nil, keepAliveSignal: nil, fromMenu: false) + let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, payload: botPayload, buttonText: nil, keepAliveSignal: nil, fromMenu: false) let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageId) controller.openUrl = { [weak self] url in @@ -15702,6 +15722,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } } + + func preloadAttachBotIcons() { + let _ = (self.context.engine.messages.attachMenuBots() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] bots in + guard let strongSelf = self else { + return + } + for bot in bots { + for (name, file) in bot.icons { + if [.iOSAnimated].contains(name), let peer = PeerReference(bot.peer) { + strongSelf.preloadAttachBotIconsDisposables.add(freeMediaFileInteractiveFetched(account: strongSelf.context.account, fileReference: .attachBot(peer: peer, media: file)).start()) + } + } + } + }) + } } private final class ContextControllerContentSourceImpl: ContextControllerContentSource { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index caef265206..966467b241 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -907,6 +907,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } + let buttonTitle: String + if case let .webView(title, _) = interfaceState.botMenuButton { + buttonTitle = title + } else { + buttonTitle = interfaceState.strings.Conversation_InputMenu + } + + self.menuButtonTextNode.attributedText = NSAttributedString(string: buttonTitle, font: Font.with(size: 16.0, design: .round, weight: .medium, traits: []), textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor) + self.menuButton.accessibilityLabel = self.menuButtonTextNode.attributedText?.string + menuTextSize = self.menuButtonTextNode.updateLayout(CGSize(width: width, height: 44.0)) + var updateSendButtonIcon = false if (previousState?.interfaceState.editMessage != nil) != (interfaceState.interfaceState.editMessage != nil) { updateSendButtonIcon = true @@ -945,17 +956,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.menuButtonBackgroundNode.backgroundColor = interfaceState.theme.chat.inputPanel.actionControlFillColor - let buttonTitle: String - if case let .webView(title, _) = interfaceState.botMenuButton { - buttonTitle = title - } else { - buttonTitle = interfaceState.strings.Conversation_InputMenu - } - - self.menuButtonTextNode.attributedText = NSAttributedString(string: buttonTitle, font: Font.with(size: 16.0, design: .round, weight: .medium, traits: []), textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor) - self.menuButton.accessibilityLabel = interfaceState.strings.Conversation_InputMenu - menuTextSize = self.menuButtonTextNode.updateLayout(CGSize(width: width, height: 44.0)) - if isEditingMedia { self.attachmentButton.setImage(PresentationResourcesChat.chatInputPanelEditAttachmentButtonImage(interfaceState.theme), for: []) } else { diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index e7aa5763fd..b417cc205d 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -137,6 +137,14 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR } return fetchPreparedPatternWallpaperRepresentation(resource: resource, resourceData: data, representation: representation) } + } else if let representation = representation as? CachedPreparedSvgRepresentation { + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> mapToSignal { data -> Signal in + if !data.complete { + return .complete() + } + return fetchPreparedSvgRepresentation(resource: resource, resourceData: data, representation: representation) + } } return .never() } @@ -755,3 +763,18 @@ private func fetchPreparedPatternWallpaperRepresentation(resource: MediaResource return EmptyDisposable }) |> runOn(Queue.concurrentDefaultQueue()) } + +private func fetchPreparedSvgRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedPreparedSvgRepresentation) -> Signal { + return Signal({ subscriber in + if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + if let data = prepareSvgImage(data) { + let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" + let url = URL(fileURLWithPath: path) + let _ = try? data.write(to: url) + subscriber.putNext(.temporaryPath(path)) + subscriber.putCompletion() + } + } + return EmptyDisposable + }) |> runOn(Queue.concurrentDefaultQueue()) +} diff --git a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift b/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift index dec0969495..ba94aa2efc 100644 --- a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift +++ b/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift @@ -55,10 +55,12 @@ private final class PlayPauseIconNode: ManagedAnimationNode { final class PlayPauseIconComponent: Component { let state: PlayPauseIconNodeState + let tintColor: UIColor? let size: CGSize - init(state: PlayPauseIconNodeState, size: CGSize) { + init(state: PlayPauseIconNodeState, tintColor: UIColor?, size: CGSize) { self.state = state + self.tintColor = tintColor self.size = size } @@ -66,6 +68,9 @@ final class PlayPauseIconComponent: Component { if lhs.state != rhs.state { return false } + if lhs.tintColor != rhs.tintColor { + return false + } if lhs.size != rhs.size { return false } @@ -94,6 +99,8 @@ final class PlayPauseIconComponent: Component { self.animationNode.enqueueState(component.state, animated: true) } + + self.animationNode.customColor = component.tintColor let animationSize = component.size let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height)) diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index 775681717b..1abaa4ef56 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -15,11 +15,10 @@ import MultilineTextComponent import BundleIconComponent import UndoUI -private func generateExpandBackground(size: CGSize) -> UIImage { +private func generateExpandBackground(size: CGSize, color: UIColor) -> UIImage { return generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - let color = UIColor.white var locations: [CGFloat] = [0.0, 1.0] let colors: [CGColor] = [color.withAlphaComponent(0.0).cgColor, color.cgColor] @@ -88,7 +87,7 @@ private final class TranslateScreenComponent: CombinedComponent { private var speechHolder: SpeechSynthesizerHolder? fileprivate var availableSpeakLanguages: Set - fileprivate var moreBackgroundImage: (CGSize, UIImage)? + fileprivate var moreBackgroundImage: (CGSize, UIImage, UIColor)? init(context: AccountContext, fromLanguage: String?, text: String, toLanguage: String, expand: @escaping () -> Void) { self.context = context @@ -115,6 +114,7 @@ private final class TranslateScreenComponent: CombinedComponent { } deinit { + self.speechHolder?.stop() self.translationDisposable.dispose() } @@ -337,6 +337,11 @@ private final class TranslateScreenComponent: CombinedComponent { if state.textExpanded { if let fromLanguage = state.fromLanguage, state.availableSpeakLanguages.contains(fromLanguage) { + var checkColor = theme.list.itemCheckColors.foregroundColor + if checkColor.rgb == theme.list.itemPrimaryTextColor.rgb { + checkColor = theme.list.plainBackgroundColor + } + let originalSpeakButton = originalSpeakButton.update( component: Button( content: AnyComponent(ZStack([ @@ -346,6 +351,7 @@ private final class TranslateScreenComponent: CombinedComponent { ))), AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( state: state.isSpeakingOriginalText ? .pause : .play, + tintColor: checkColor, size: CGSize(width: 18.0, height: 18.0) ))), ])), @@ -381,14 +387,15 @@ private final class TranslateScreenComponent: CombinedComponent { let originalMoreBackgroundSize = CGSize(width: originalMoreButton.size.width + 50.0, height: originalMoreButton.size.height) let originalMoreBackgroundImage: UIImage - if let (size, image) = state.moreBackgroundImage, size == originalMoreBackgroundSize { + let backgroundColor = theme.list.itemBlocksBackgroundColor + if let (size, image, color) = state.moreBackgroundImage, size == originalMoreBackgroundSize && color == backgroundColor { originalMoreBackgroundImage = image } else { - originalMoreBackgroundImage = generateExpandBackground(size: originalMoreBackgroundSize) - state.moreBackgroundImage = (originalMoreBackgroundSize, originalMoreBackgroundImage) + originalMoreBackgroundImage = generateExpandBackground(size: originalMoreBackgroundSize, color: backgroundColor) + state.moreBackgroundImage = (originalMoreBackgroundSize, originalMoreBackgroundImage, backgroundColor) } let originalMoreBackground = originalMoreBackground.update( - component: Image(image: originalMoreBackgroundImage, tintColor: theme.list.itemBlocksBackgroundColor), + component: Image(image: originalMoreBackgroundImage, tintColor: backgroundColor), availableSize: originalMoreBackgroundSize, transition: .immediate ) @@ -426,6 +433,7 @@ private final class TranslateScreenComponent: CombinedComponent { ))), AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( state: state.isSpeakingTranslatedText ? .pause : .play, + tintColor: theme.list.itemCheckColors.foregroundColor, size: CGSize(width: 18.0, height: 18.0) ))), ])), diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index ebcabbb882..f3b2a3cc15 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -531,7 +531,7 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete var image: UIImage? if let fullSizeData = fullSizeData { if mode == .screen { - image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale)) + image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0) } else { image = UIImage(data: fullSizeData) } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index a835e87752..b1fc7dab20 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -24,6 +24,7 @@ public struct WebAppParameters { let botName: String let url: String? let queryId: Int64? + let payload: String? let buttonText: String? let keepAliveSignal: Signal? let fromMenu: Bool @@ -34,6 +35,7 @@ public struct WebAppParameters { botName: String, url: String?, queryId: Int64?, + payload: String?, buttonText: String?, keepAliveSignal: Signal?, fromMenu: Bool @@ -43,6 +45,7 @@ public struct WebAppParameters { self.botName = botName self.url = url self.queryId = queryId + self.payload = payload self.buttonText = buttonText self.keepAliveSignal = keepAliveSignal self.fromMenu = fromMenu @@ -75,7 +78,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private weak var controller: WebAppController? fileprivate var webView: WebAppWebView? - private var placeholderIcon: UIImage? + private var placeholderIcon: (UIImage, Bool)? private var placeholderNode: ShimmerEffectNode? fileprivate let loadingProgressPromise = Promise(nil) @@ -124,22 +127,32 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let strongSelf = self else { return } - var iconFile: TelegramMediaFile? - if let file = bot.icons[.iOSStatic] { - iconFile = file + var imageFile: TelegramMediaFile? + var isPlaceholder = false + if let file = bot.icons[.placeholder] { + imageFile = file + isPlaceholder = true + } else if let file = bot.icons[.iOSStatic] { + imageFile = file } else if let file = bot.icons[.default] { - iconFile = file + imageFile = file } - if let iconFile = iconFile, let peer = PeerReference(bot.peer) { - let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, fileReference: .attachBot(peer: peer, media: iconFile)).start() - strongSelf.iconDisposable = (svgIconImageFile(account: strongSelf.context.account, fileReference: .attachBot(peer: peer, media: iconFile)) + if let imageFile = imageFile, let peer = PeerReference(bot.peer) { + let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, fileReference: .attachBot(peer: peer, media: imageFile)).start() + strongSelf.iconDisposable = (svgIconImageFile(account: strongSelf.context.account, fileReference: .attachBot(peer: peer, media: imageFile), stickToTop: isPlaceholder) |> deliverOnMainQueue).start(next: { [weak self] transform in if let strongSelf = self { - let imageSize = CGSize(width: 75.0, height: 75.0) + let imageSize: CGSize + if isPlaceholder, let (layout, _) = strongSelf.validLayout { + let minSize = min(layout.size.width, layout.size.height) + imageSize = CGSize(width: minSize, height: minSize * 3.0) + } else { + imageSize = CGSize(width: 75.0, height: 75.0) + } let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) let drawingContext = transform(arguments) if let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) { - strongSelf.placeholderIcon = image + strongSelf.placeholderIcon = (image, isPlaceholder) if let (layout, navigationBarHeight) = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } @@ -168,7 +181,7 @@ public final class WebAppController: ViewController, AttachmentContainable { }) } } else { - let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: controller.fromMenu, replyToMessageId: controller.replyToMessageId) + let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, payload: controller.payload, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: controller.fromMenu, replyToMessageId: controller.replyToMessageId) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return @@ -213,31 +226,15 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.sendEvent(name: "main_button_pressed", data: nil) } - private func updatePlaceholder(grid: Bool, layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + private func updatePlaceholder(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { var shapes: [ShimmerEffect.ShimmerEffectNode.Shape] = [] var placeholderSize: CGSize = CGSize() - if grid { - let spacing: CGFloat = 8.0 - let cols: Int = min(4, Int(floor(layout.size.width / 118.0))) - let itemSize: CGSize = CGSize(width: cols == 4 ? 111.0 : 118.0, height: 150.0) - let rows: Int = 4 - - let sideInset = floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - itemSize.width * CGFloat(cols) - spacing * CGFloat(cols - 1)) / 2.0) - - for row in 0 ..< rows { - for col in 0 ..< cols { - shapes.append(.roundedRect(rect: CGRect(x: layout.safeInsets.left + sideInset + CGFloat(col) * (itemSize.width + spacing), y: navigationBarHeight + CGFloat(row) * (itemSize.height + spacing), width: itemSize.width, height: itemSize.height), cornerRadius: 10.0)) - } - } - - placeholderSize = layout.size - } else { - if let icon = self.placeholderIcon { - shapes = [.image(image: icon, rect: CGRect(origin: CGPoint(), size: icon.size))] - placeholderSize = icon.size - } - } + if let (image, _) = self.placeholderIcon { + shapes = [.image(image: image, rect: CGRect(origin: CGPoint(), size: image.size))] + placeholderSize = image.size + } + let theme = self.presentationData.theme self.placeholderNode?.update(backgroundColor: self.backgroundColor ?? .clear, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: placeholderSize) @@ -263,38 +260,29 @@ public final class WebAppController: ViewController, AttachmentContainable { } return nil } + + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + Queue.mainQueue().after(0.65, { + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) + transition.updateAlpha(layer: webView.layer, alpha: 1.0) + if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + transition.updateAlpha(node: placeholderNode, alpha: 0.0, completion: { [weak placeholderNode] _ in + placeholderNode?.removeFromSupernode() + }) + } - private var loadCount = 0 - func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - self.loadCount += 1 - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - self.loadCount -= 1 - - Queue.mainQueue().after(0.1, { - if self.loadCount == 0, let webView = self.webView { - let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) - transition.updateAlpha(layer: webView.layer, alpha: 1.0) - if let placeholderNode = self.placeholderNode { - self.placeholderNode = nil - transition.updateAlpha(node: placeholderNode, alpha: 0.0, completion: { [weak placeholderNode] _ in - placeholderNode?.removeFromSupernode() - }) - } + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } }) - - if let (layout, navigationBarHeight) = self.validLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } } @available(iOSApplicationExtension 15.0, iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.prompt) } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentOffset = scrollView.contentOffset.y self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) @@ -308,7 +296,14 @@ public final class WebAppController: ViewController, AttachmentContainable { if let webView = self.webView { let frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom))) let viewportFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom))) - transition.updateFrame(view: webView, frame: frame) + + if previousLayout != nil && (previousLayout?.inputHeight ?? 0.0).isZero, let inputHeight = layout.inputHeight, inputHeight > 44.0, transition.isAnimated { + Queue.mainQueue().after(0.4, { + transition.updateFrame(view: webView, frame: frame) + }) + } else { + transition.updateFrame(view: webView, frame: frame) + } webView.updateFrame(frame: viewportFrame, transition: transition) } @@ -321,9 +316,13 @@ public final class WebAppController: ViewController, AttachmentContainable { height = layout.size.height - layout.intrinsicInsets.bottom } - let grid = self.controller?.botId.id._internalGetInt64Value() == 2200339955 - let placeholderSize = self.updatePlaceholder(grid: grid, layout: layout, navigationBarHeight: navigationBarHeight, transition: transition) - let placeholderY: CGFloat = grid ? 0.0 : floorToScreenPixels((height - placeholderSize.height) / 2.0) + let placeholderSize = self.updatePlaceholder(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition) + let placeholderY: CGFloat + if let (_, isPlaceholder) = self.placeholderIcon, isPlaceholder { + placeholderY = navigationBarHeight + } else { + placeholderY = floorToScreenPixels((height - placeholderSize.height) / 2.0) + } let placeholderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - placeholderSize.width) / 2.0), y: placeholderY), size: placeholderSize) transition.updateFrame(node: placeholderNode, frame: placeholderFrame) placeholderNode.updateAbsoluteRect(placeholderFrame, within: layout.size) @@ -372,7 +371,8 @@ public final class WebAppController: ViewController, AttachmentContainable { let textColor = textColorString.flatMap({ UIColor(hexString: $0) }) ?? self.presentationData.theme.list.itemCheckColors.foregroundColor let isLoading = json["is_progress_visible"] as? Bool - let state = AttachmentMainButtonState(text: text, backgroundColor: backgroundColor, textColor: textColor, isVisible: isVisible, isLoading: isLoading ?? false) + let isEnabled = json["is_active"] as? Bool + let state = AttachmentMainButtonState(text: text, backgroundColor: backgroundColor, textColor: textColor, isVisible: isVisible, isLoading: isLoading ?? false, isEnabled: isEnabled ?? true) self.mainButtonStatePromise.set(.single(state)) } } @@ -448,6 +448,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private let botId: PeerId private let url: String? private let queryId: Int64? + private let payload: String? private let buttonText: String? private let fromMenu: Bool private let keepAliveSignal: Signal? @@ -467,6 +468,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.botId = params.botId self.url = params.url self.queryId = params.queryId + self.payload = params.payload self.buttonText = params.buttonText self.fromMenu = params.fromMenu self.keepAliveSignal = params.keepAliveSignal @@ -666,7 +668,7 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent } } -public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, openUrl: @escaping (String) -> Void, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, dismissed: @escaping () -> Void = {}) -> ViewController { +public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, openUrl: @escaping (String) -> Void, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu) controller.getInputContainerNode = getInputContainerNode controller.requestController = { _, present in @@ -675,6 +677,7 @@ public func standaloneWebAppController(context: AccountContext, updatedPresentat webAppController.completion = completion present(webAppController, webAppController.mediaPickerContext) } - controller.dismissed = dismissed + controller.willDismiss = willDismiss + controller.didDismiss = didDismiss return controller }