diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 9b119f2058..757d16d92f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12535,3 +12535,5 @@ Sorry for the inconvenience."; "Stars.Transaction.FragmentUnknown_URL" = "https://fragment.com/stars"; "Conversation.StatusBotSubscribers_1" = "1 user"; "Conversation.StatusBotSubscribers_any" = "%d users"; + +"Story.Editor.Add" = "Add"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 4f4f791b3f..11293c9f74 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1060,10 +1060,12 @@ public protocol SharedAccountContext: AnyObject { func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController + func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController + func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController - func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController + func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController diff --git a/submodules/BrowserUI/Sources/BrowserContent.swift b/submodules/BrowserUI/Sources/BrowserContent.swift index 931103bc78..7a6427c01d 100644 --- a/submodules/BrowserUI/Sources/BrowserContent.swift +++ b/submodules/BrowserUI/Sources/BrowserContent.swift @@ -153,6 +153,7 @@ protocol BrowserContent: UIView { var getNavigationController: () -> NavigationController? { get set } var minimize: () -> Void { get set } + var close: () -> Void { get set } var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set } diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index 2490767e14..f9e9e721ef 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -67,6 +67,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var pushContent: (BrowserScreen.Subject) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } + var close: () -> Void = { } var openPeer: (EnginePeer) -> Void = { _ in } diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 2d9bd49d46..78637c1ba6 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -521,6 +521,16 @@ public class BrowserScreen: ViewController, MinimizableController { } self.minimize() } + browserContent.close = { [weak self] in + guard let self, let controller = self.controller else { + return + } + if controller.isMinimized { + + } else { + controller.dismiss() + } + } self.content.append(browserContent) self.requestLayout(transition: transition) @@ -952,6 +962,9 @@ public class BrowserScreen: ViewController, MinimizableController { private let context: AccountContext private let subject: Subject + + var openPreviousOnClose = false + public init(context: AccountContext, subject: Subject) { self.context = context self.subject = subject diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 9a30d01b79..1a313e2e87 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -140,6 +140,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU var pushContent: (BrowserScreen.Subject) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var minimize: () -> Void = { } + var close: () -> Void = { } var present: (ViewController, Any?) -> Void = { _, _ in } var presentInGlobalOverlay: (ViewController) -> Void = { _ in } var getNavigationController: () -> NavigationController? = { return nil } @@ -191,6 +192,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU if #available(iOS 15.0, *) { self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } + if #available(iOS 16.4, *) { + self.webView.isInspectable = true + } self.addSubview(self.webView) } @@ -217,35 +221,54 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU self.updateLayout(size: size, insets: insets, transition: .immediate) } } + + private let setupFontFunctions = """ + (function() { + const styleId = 'telegram-font-overrides'; + + function setTelegramFontOverrides(font, textSizeAdjust) { + let style = document.getElementById(styleId); + + if (!style) { + style = document.createElement('style'); + style.id = styleId; + document.head.appendChild(style); + } + + let cssRules = '* {'; + if (font !== null) { + cssRules += ` + font-family: ${font} !important; + `; + } + if (textSizeAdjust !== null) { + cssRules += ` + -webkit-text-size-adjust: ${textSizeAdjust} !important; + `; + } + cssRules += '}'; + + style.innerHTML = cssRules; + + if (font === null && textSizeAdjust === null) { + style.parentNode.removeChild(style); + } + } + window.setTelegramFontOverrides = setTelegramFontOverrides; + })(); + """ var currentFontState = BrowserPresentationState.FontState(size: 100, isSerif: false) func updateFontState(_ state: BrowserPresentationState.FontState) { self.updateFontState(state, force: false) } func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) { - if self.currentFontState.size != state.size || (force && self.currentFontState.size != 100) { - self.setFontSize(state.size) - } - if self.currentFontState.isSerif != state.isSerif || (force && self.currentFontState.isSerif) { - self.setFontSerif(state.isSerif) - } self.currentFontState = state - } - - private func setFontSize(_ fontSize: Int32) { - let js = "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='\(fontSize)%'" - self.webView.evaluateJavaScript(js, completionHandler: nil) - } - - private func setFontSerif(_ force: Bool) { - let js: String - if force { - js = "document.getElementsByTagName(\'body\')[0].style.fontFamily = 'Georgia, serif';" - } else { - js = "document.getElementsByTagName(\'body\')[0].style.fontFamily = '\"Lucida Grande\", \"Lucida Sans Unicode\", Arial, Helvetica, Verdana, sans-serif';" - } - self.webView.evaluateJavaScript(js) { _, _ in - } + + let fontFamily = state.isSerif ? "'Georgia, serif'" : "null" + let textSizeAdjust = state.size != 100 ? "'\(state.size)%'" : "null" + let js = "\(setupFontFunctions) setTelegramFontOverrides(\(fontFamily), \(textSizeAdjust))"; + self.webView.evaluateJavaScript(js) { _, _ in } } private var didSetupSearch = false @@ -513,6 +536,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + if navigationAction.targetFrame == nil { + if let url = navigationAction.request.url?.absoluteString { + self.open(url: url, new: true) + } + } + return nil + } + + func webViewDidClose(_ webView: WKWebView) { + self.close() + } + @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) @@ -619,8 +655,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU private func open(url: String, new: Bool) { let subject: BrowserScreen.Subject = .webPage(url: url) if new, let navigationController = self.getNavigationController() { + navigationController._keepModalDismissProgress = true self.minimize() let controller = BrowserScreen(context: self.context, subject: subject) + navigationController._keepModalDismissProgress = true navigationController.pushViewController(controller) } else { self.pushContent(subject) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 4c55b16c03..910d42bf28 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -91,7 +91,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let context: AccountContext private let peersFilter: ChatListNodePeersFilter private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? - private let location: ChatListControllerLocation + private var location: ChatListControllerLocation private let displaySearchFilters: Bool private let hasDownloads: Bool private var interaction: ChatListSearchInteraction? @@ -577,6 +577,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo for token in tokens { tokensIdSet.insert(token.id) } + + if case .chatList(.archive) = self.location, !tokens.contains(where: { $0.id == AnyHashable(ChatListTokenId.archive.rawValue) }) { + self.location = .chatList(groupId: .root) + self.paneContainerNode.location = self.location + } + if !tokensIdSet.contains(ChatListTokenId.date.rawValue) && updatedOptions?.date != nil { updatedOptions = updatedOptions?.withUpdatedDate(nil) } @@ -590,7 +596,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo var options = options var tokens: [SearchBarToken] = [] if case .chatList(.archive) = self.location { - tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true)) + tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false)) } else if case .forum = self.location, let forumPeer = self.forumPeer { tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true)) } @@ -670,7 +676,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo var tokens: [SearchBarToken] = [] if case .chatList(.archive) = self.location { - tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true)) + tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false)) } else if case .forum = self.location, let forumPeer = self.forumPeer { tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true)) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index e6ae53e812..ccf0563396 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -159,7 +159,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD private let updatedPresentationData: (initial: PresentationData, signal: Signal)? private let peersFilter: ChatListNodePeersFilter private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? - private let location: ChatListControllerLocation + var location: ChatListControllerLocation private let searchQuery: Signal private let searchOptions: Signal private let globalPeerSearchContext: GlobalPeerSearchContext diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 5393185f88..c0f1930656 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -512,6 +512,10 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture } } else { self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentSize.height / 3.0), animated: true) + + if let chatController = self.baseNavigationController()?.topViewController as? ChatController { + chatController.updatePushedTransition(1.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0))) + } } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index f3d483c191..ae44cd0bf1 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2982,12 +2982,16 @@ public func mediaPickerController( public func storyMediaPickerController( context: AccountContext, + isDark: Bool, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void ) -> ViewController { - let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) + var presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + if isDark { + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + } let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { return nil diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index c3bc3a2eb4..5d42d5677e 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -2986,7 +2986,7 @@ public class CameraScreen: ViewController { if let current = self.galleryController { controller = current } else { - controller = self.context.sharedContext.makeStoryMediaPickerScreen(context: self.context, getSourceRect: { [weak self] in + controller = self.context.sharedContext.makeStoryMediaPickerScreen(context: self.context, isDark: true, getSourceRect: { [weak self] in if let self { if let galleryButton = self.node.componentHost.findTaggedView(tag: galleryButtonTag) { return galleryButton.convert(galleryButton.bounds, to: self.view).offsetBy(dx: 0.0, dy: -15.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 460de318a1..6bee292fad 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1543,7 +1543,14 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } - let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments) + var videoCorners = corners + var imageCorners = corners + if let file = media as? TelegramMediaFile, file.isInstantVideo { + videoCorners = ImageCorners(radius: boundingSize.width / 2.0) + imageCorners = ImageCorners(radius: 0.0) + } + + let arguments = TransformImageArguments(corners: imageCorners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments) let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize).ensuredValid let cleanImageFrame = CGRect(origin: imageFrame.origin, size: CGSize(width: imageFrame.width - arguments.corners.extendedEdges.right, height: imageFrame.height)) @@ -1563,7 +1570,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr strongSelf.automaticPlayback = automaticPlayback strongSelf.automaticDownload = automaticDownload strongSelf.preferredStoryHighQuality = associatedData.preferredStoryHighQuality - + if let previousArguments = strongSelf.currentImageArguments { if previousArguments.imageSize == arguments.imageSize { strongSelf.pinchContainerNode.frame = imageFrame @@ -1616,7 +1623,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr statusFrame.origin.y = floor(imageFrame.height / 2.0 - statusFrame.height / 2.0) statusNode.frame = statusFrame } - + var updatedVideoNodeReadySignal: Signal? var updatedPlayerStatusSignal: Signal? if let currentReplaceVideoNode = replaceVideoNode { @@ -1628,7 +1635,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } if currentReplaceVideoNode, let updatedVideoFile = updateVideoFile { - let decoration = ChatBubbleVideoDecoration(corners: arguments.corners, nativeSize: nativeSize, contentMode: contentMode.bubbleVideoDecorationContentMode, backgroundColor: arguments.emptyColor ?? .black) + let decoration = ChatBubbleVideoDecoration(corners: videoCorners, nativeSize: nativeSize, contentMode: contentMode.bubbleVideoDecorationContentMode, backgroundColor: arguments.emptyColor ?? .black) strongSelf.videoNodeDecoration = decoration let mediaManager = context.sharedContext.mediaManager @@ -1700,10 +1707,17 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }), strongSelf.extendedMediaOverlayNode == nil { strongSelf.internallyVisible = false } - + if let videoNode = strongSelf.videoNode { - if !(replaceVideoNode ?? false), let decoration = videoNode.decoration as? ChatBubbleVideoDecoration, decoration.corners != corners { - decoration.updateCorners(corners) + if !(replaceVideoNode ?? false), let decoration = videoNode.decoration as? ChatBubbleVideoDecoration, decoration.corners != videoCorners { + decoration.updateCorners(videoCorners) + } + + if !videoCorners.isEmpty && imageCorners.isEmpty { + strongSelf.imageNode.clipsToBounds = true + strongSelf.imageNode.cornerRadius = videoCorners.topLeft.radius + } else { + strongSelf.imageNode.cornerRadius = 0.0 } videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index baf6aeba3e..18a9f0f686 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -818,7 +818,7 @@ final class MediaEditorScreenComponent: Component { } var doneButtonTitle: String? - var doneButtonIcon: UIImage + var doneButtonIcon: UIImage? switch controller.mode { case .storyEditor: doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased() @@ -826,6 +826,10 @@ final class MediaEditorScreenComponent: Component { case .stickerEditor: doneButtonTitle = nil doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! + case .botPreview: + //TODO:localize + doneButtonTitle = environment.strings.Story_Editor_Add + doneButtonIcon = nil } let doneButtonSize = self.doneButton.update( @@ -859,6 +863,8 @@ final class MediaEditorScreenComponent: Component { } case .stickerEditor: controller.requestStickerCompletion(animated: true) + case .botPreview: + controller.requestStoryCompletion(animated: true) } } )), @@ -2415,6 +2421,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case storyEditor case stickerEditor(mode: StickerEditorMode) + case botPreview } public enum TransitionIn { @@ -2873,7 +2880,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let mediaEntity = DrawingMediaEntity(size: fittedSize) mediaEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0) switch controller.mode { - case .storyEditor: + case .storyEditor, .botPreview: if fittedSize.height > fittedSize.width { mediaEntity.scale = max(storyDimensions.width / fittedSize.width, storyDimensions.height / fittedSize.height) } else { @@ -3626,7 +3633,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate transitionInView.contentMode = .scaleAspectFill var initialScale: CGFloat switch controller.mode { - case .storyEditor: + case .storyEditor, .botPreview: if image.size.height > image.size.width { initialScale = max(self.previewContainerView.bounds.width / image.size.width, self.previewContainerView.bounds.height / image.size.height) } else { @@ -4784,12 +4791,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate mediaEditor.maybePauseVideo() var hasInteractiveStickers = true - if let controller = self.controller, case .stickerEditor = controller.mode { - hasInteractiveStickers = false + if let controller = self.controller { + switch controller.mode { + case .stickerEditor, .botPreview: + hasInteractiveStickers = false + default: + break + } } var weatherSignal: Signal - if "".isEmpty { + if hasInteractiveStickers { let weatherPromise: Promise if let current = self.weatherPromise { weatherPromise = current @@ -6101,7 +6113,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate save = presentationData.strings.Story_Editor_DraftKeepMedia } text = presentationData.strings.Story_Editor_DraftDiscaedText - case .stickerEditor: + case .stickerEditor, .botPreview: title = presentationData.strings.Story_Editor_DraftDiscardMedia text = presentationData.strings.Story_Editor_DiscardText } @@ -7440,12 +7452,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private final class DoneButtonContentComponent: CombinedComponent { let backgroundColor: UIColor - let icon: UIImage + let icon: UIImage? let title: String? init( backgroundColor: UIColor, - icon: UIImage, + icon: UIImage?, title: String? ) { self.backgroundColor = backgroundColor @@ -7469,12 +7481,14 @@ private final class DoneButtonContentComponent: CombinedComponent { let text = Child(Text.self) return { context in - let iconSize = context.component.icon.size - let icon = icon.update( - component: Image(image: context.component.icon, tintColor: .white, size: iconSize), - availableSize: CGSize(width: 180.0, height: 100.0), - transition: .immediate - ) + var iconChild: _UpdatedChildComponent? + if let iconImage = context.component.icon { + iconChild = icon.update( + component: Image(image: iconImage, tintColor: .white, size: iconImage.size), + availableSize: CGSize(width: 180.0, height: 100.0), + transition: .immediate + ) + } let backgroundHeight: CGFloat = 33.0 var backgroundSize = CGSize(width: backgroundHeight, height: backgroundHeight) @@ -7494,7 +7508,10 @@ private final class DoneButtonContentComponent: CombinedComponent { transition: .immediate ) - let updatedBackgroundWidth = backgroundSize.width + textSpacing + title!.size.width + var updatedBackgroundWidth = backgroundSize.width + title!.size.width + if let _ = iconChild { + updatedBackgroundWidth += textSpacing + } if updatedBackgroundWidth < 126.0 { backgroundSize.width = updatedBackgroundWidth } else { @@ -7514,16 +7531,22 @@ private final class DoneButtonContentComponent: CombinedComponent { ) if let title { + var titlePosition = backgroundSize.width / 2.0 + if let _ = iconChild { + titlePosition = title.size.width / 2.0 + 15.0 + } context.add(title - .position(CGPoint(x: title.size.width / 2.0 + 15.0, y: backgroundHeight / 2.0)) + .position(CGPoint(x: titlePosition, y: backgroundHeight / 2.0)) .opacity(hideTitle ? 0.0 : 1.0) ) } - context.add(icon - .position(CGPoint(x: background.size.width - 16.0, y: backgroundSize.height / 2.0)) - ) - + if let iconChild { + context.add(iconChild + .position(CGPoint(x: background.size.width - 16.0, y: backgroundSize.height / 2.0)) + ) + } + return backgroundSize } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift index 0a0f0bfed5..8236b696c2 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift @@ -58,9 +58,10 @@ func getWeather(context: AccountContext) -> Signal mapToSignal { weather in if let weather { - if let match = context.animatedEmojiStickersValue[weather.emoji.strippedEmoji]?.first { + let effectiveEmoji = emojiFor(for: weather.emoji.strippedEmoji, date: Date(), location: location) + if let match = context.animatedEmojiStickersValue[effectiveEmoji]?.first { return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather( - emoji: weather.emoji.strippedEmoji, + emoji: effectiveEmoji, emojiFile: match.file, temperature: weather.temperature ))) @@ -97,3 +98,123 @@ private struct WeatherBotConfiguration { } } } + +private let J1970: Double = 2440588.0 +private let moonEmojis = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑"] + +private func emojiFor(for emoji: String, date: Date, location: CLLocationCoordinate2D) -> String { + var emoji = emoji + if !"".isEmpty, ["☀️", "🌤️"].contains(emoji) && !isDay(latitude: location.latitude, longitude: location.longitude, dateTime: date) { + emoji = moonPhaseEmoji(for: date) + } + return emoji +} + +private func moonPhaseEmoji(for date: Date) -> String { + let julianDate = toJulianDate(date: date) + + let referenceNewMoon: Double = 2451550.1 + let synodicMonth: Double = 29.53058867 + + let daysSinceNewMoon = julianDate - referenceNewMoon + let newMoons = daysSinceNewMoon / synodicMonth + let currentMoonPhase = (newMoons - floor(newMoons)) * synodicMonth + + switch currentMoonPhase { + case 0..<1.84566: + return moonEmojis[0] + case 1.84566..<5.53699: + return moonEmojis[1] + case 5.53699..<9.22831: + return moonEmojis[2] + case 9.22831..<12.91963: + return moonEmojis[3] + case 12.91963..<16.61096: + return moonEmojis[4] + case 16.61096..<20.30228: + return moonEmojis[5] + case 20.30228..<23.99361: + return moonEmojis[6] + case 23.99361..<27.68493: + return moonEmojis[7] + default: + return moonEmojis[8] + } +} + +private func isDay(latitude: Double, longitude: Double, dateTime: Date) -> Bool { + let calendar = Calendar.current + let date = calendar.startOfDay(for: dateTime) + let time = dateTime.timeIntervalSince(date) + + let sunrise = calculateSunrise(latitude: latitude, longitude: longitude, date: date) + let sunset = calculateSunset(latitude: latitude, longitude: longitude, date: date) + + return time >= sunrise * 3600 && time <= sunset * 3600 +} + +private func calculateSunrise(latitude: Double, longitude: Double, date: Date) -> Double { + return calculateSunTime(latitude: latitude, longitude: longitude, date: date, isSunrise: true) +} + +private func calculateSunset(latitude: Double, longitude: Double, date: Date) -> Double { + return calculateSunTime(latitude: latitude, longitude: longitude, date: date, isSunrise: false) +} + +private func calculateSunTime(latitude: Double, longitude: Double, date: Date, isSunrise: Bool) -> Double { + let calendar = Calendar.current + let dayOfYear = calendar.ordinality(of: .day, in: .year, for: date)! + let zenith = 90.833 + + let D2R = Double.pi / 180.0 + let R2D = 180.0 / Double.pi + + let lngHour = longitude / 15.0 + let t = Double(dayOfYear) + ((isSunrise ? 6.0 : 18.0) - lngHour) / 24.0 + + let M = (0.9856 * t) - 3.289 + var L = M + (1.916 * sin(M * D2R)) + (0.020 * sin(2 * M * D2R)) + 282.634 + + if L > 360.0 { + L -= 360.0 + } else if L < 0.0 { + L += 360.0 + } + + var RA = R2D * atan(0.91764 * tan(L * D2R)) + if RA > 360.0 { + RA -= 360.0 + } else if RA < 0.0 { + RA += 360.0 + } + + let Lquadrant = (floor(L / 90.0)) * 90.0 + let RAquadrant = (floor(RA / 90.0)) * 90.0 + RA += (Lquadrant - RAquadrant) + RA /= 15.0 + + let sinDec = 0.39782 * sin(L * D2R) + let cosDec = cos(asin(sinDec)) + + let cosH = (cos(zenith * D2R) - (sinDec * sin(latitude * D2R))) / (cosDec * cos(latitude * D2R)) + if cosH > 1.0 || cosH < -1.0 { + return -1 + } + + var H = isSunrise ? (360.0 - R2D * acos(cosH)) : R2D * acos(cosH) + H /= 15.0 + + let T = H + RA - (0.06571 * t) - 6.622 + var UT = T - lngHour + + if UT > 24.0 { + UT -= 24.0 + } else if UT < 0.0 { + UT += 24.0 + } + return UT +} + +private func toJulianDate(date: Date) -> Double { + return date.timeIntervalSince1970 / 86400.0 + J1970 - 0.5 +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 9a3c72e377..16b883aeca 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -9801,6 +9801,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.push(controller) } + private func openBotPreviewEditor(target: Stories.PendingTarget, source: Any, transitionIn: (UIView, CGRect, UIImage?)?) { + let context = self.context + + let externalState = MediaEditorTransitionOutExternalState( + storyTarget: target, + isForcedTarget: false, + isPeerArchived: false, + transitionOut: nil + ) + + let controller = context.sharedContext.makeBotPreviewEditorScreen( + context: context, + source: source, + target: target, + transitionArguments: transitionIn, + externalState: externalState, + completion: { result, commit in + if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { + var viewControllers = rootController.viewControllers + viewControllers = viewControllers.filter { !($0 is AttachmentController)} + rootController.setViewControllers(viewControllers, animated: false) + + rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit) + } + }, + cancelled: {} + ) + self.controller?.push(controller) + } + private func openPostStory(sourceFrame: CGRect?) { self.postingAvailabilityDisposable?.dispose() @@ -9808,12 +9838,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if !botInfo.flags.contains(.canEdit) { return } - let cameraTransitionIn: StoryCameraTransitionIn? = nil - - if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { - let coordinator = rootController.openStoryCamera(customTarget: .botPreview(self.peerId), transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut()) - coordinator?.animateIn() - } + let controller = self.context.sharedContext.makeStoryMediaPickerScreen( + context: self.context, + isDark: false, + getSourceRect: { return .zero }, + completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in + guard let self else { + return + } + self.openBotPreviewEditor(target: .botPreview(self.peerId), source: result, transitionIn: (transitionView, transitionRect, transitionImage)) + }, + dismissed: {}, + groupsPresented: {} + ) + self.controller?.push(controller) } else { let canPostStatus: Signal canPostStatus = self.context.engine.messages.checkStoriesUploadAvailability(target: .peer(self.peerId)) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift index ab69237e03..0a2dd06b11 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift @@ -567,11 +567,7 @@ final class StoryItemOverlaysView: UIView { size: CGSize, cornerRadius: CGFloat, isActive: Bool - ) { - self.backgroundView.backgroundColor = flags.contains(.isDark) ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff) - self.backgroundView.frame = CGRect(origin: .zero, size: size) - self.backgroundView.layer.cornerRadius = cornerRadius - + ) -> CGSize { let itemSize = CGSize(width: floor(size.height * 0.71), height: floor(size.height * 0.71)) if self.file?.fileId != emojiFile?.fileId, let file = emojiFile { @@ -615,14 +611,19 @@ final class StoryItemOverlaysView: UIView { MultilineTextComponent(text: .plain(string)) ), environment: {}, - containerSize: size + containerSize: CGSize(width: .greatestFiniteMagnitude, height: size.height) ) + let leftInset = size.height * 0.058 + let rightInset = size.height * 0.2 + let spacing = size.height * 0.205 + let contentWidth: CGFloat = leftInset + itemSize.width + spacing + textSize.width + rightInset + if let view = self.text.view { if view.superview == nil { self.addSubview(view) } - let textFrame = CGRect(origin: CGPoint(x: size.width - textSize.width - size.height * 0.2, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: contentWidth - textSize.width - rightInset, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) let textTransition = ComponentTransition.immediate textTransition.setFrame(view: view, frame: textFrame) } @@ -632,7 +633,7 @@ final class StoryItemOverlaysView: UIView { self.addSubview(directStickerView) } - let stickerFrame = itemSize.centered(around: CGPoint(x: size.height * 0.5 + size.height * 0.058, y: size.height * 0.5)) + let stickerFrame = itemSize.centered(around: CGPoint(x: size.height * 0.5 + leftInset, y: size.height * 0.5)) let stickerTransition = ComponentTransition.immediate stickerTransition.setPosition(view: directStickerView, position: stickerFrame.center) @@ -640,6 +641,14 @@ final class StoryItemOverlaysView: UIView { directStickerView.externalShouldPlay = isActive } + + let contentSize = CGSize(width: contentWidth, height: size.height) + + self.backgroundView.backgroundColor = flags.contains(.isDark) ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff) + self.backgroundView.frame = CGRect(origin: .zero, size: contentSize) + self.backgroundView.layer.cornerRadius = cornerRadius + + return contentSize } } @@ -754,15 +763,12 @@ final class StoryItemOverlaysView: UIView { itemView = current } else { itemView = WeatherView(frame: CGRect()) + itemView.isUserInteractionEnabled = false self.itemViews[itemId] = itemView self.addSubview(itemView) } - - transition.setPosition(view: itemView, position: itemFrame.center) - transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) - transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(itemRotation, 0.0, 0.0, 1.0)) - - itemView.update( + + let itemSize = itemView.update( context: context, emoji: emoji, emojiFile: context.animatedEmojiStickersValue[emoji]?.first?.file, @@ -774,6 +780,10 @@ final class StoryItemOverlaysView: UIView { isActive: isActive ) + transition.setPosition(view: itemView, position: itemFrame.center) + transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: itemSize)) + transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(itemRotation, 0.0, 0.0, 1.0)) + nextId += 1 default: break diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 4c99a57e34..245323180c 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -32,6 +32,7 @@ import ChatMessageNotificationItem import PhoneNumberFormat import AttachmentUI import MinimizedContainer +import BrowserUI final class UnauthorizedApplicationContext { let sharedContext: SharedAccountContextImpl @@ -445,6 +446,8 @@ final class AuthorizedApplicationContext { minimizedContainer.collapse() } else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController { topContoller.minimizeIfNeeded() + } else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen { + topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil) } for controller in strongSelf.rootController.viewControllers { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d1f3cd3e2d..3ef00374d4 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2533,6 +2533,46 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } + public func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController { + let subject: Signal + if let asset = source as? PHAsset { + subject = .single(.asset(asset)) + } else if let image = source as? UIImage { + subject = .single(.image(image, PixelDimensions(image.size), nil, .bottomRight)) + } else { + subject = .single(.empty(PixelDimensions(width: 1080, height: 1920))) + } + let editorController = MediaEditorScreen( + context: context, + mode: .botPreview, + subject: subject, + customTarget: nil, + transitionIn: transitionArguments.flatMap { .gallery( + MediaEditorScreen.TransitionIn.GalleryTransitionIn( + sourceView: $0.0, + sourceRect: $0.1, + sourceImage: $0.2 + ) + ) }, + transitionOut: { finished, isNew in + if !finished, let transitionArguments { + return MediaEditorScreen.TransitionOut( + destinationView: transitionArguments.0, + destinationRect: transitionArguments.0.bounds, + destinationCornerRadius: 0.0 + ) + } + return nil + }, completion: { result, commit in + completion(result, commit) + } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void + ) + editorController.cancelled = { _ in + cancelled() + } + return editorController + } + public func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController { let subject: Signal var mode: MediaEditorScreen.Mode.StickerEditorMode @@ -2605,8 +2645,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion) } - public func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController { - return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented) + public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController { + return storyMediaPickerController(context: context, isDark: isDark, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented) } public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {