diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index 334744c553..2839f9364c 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -21,7 +21,6 @@ public final class AvatarStoryIndicatorComponent: Component { public let activeLineWidth: CGFloat public let inactiveLineWidth: CGFloat public let isGlassBackground: Bool - public let backgroundColor: UIColor? public let counters: Counters? public init( @@ -31,7 +30,6 @@ public final class AvatarStoryIndicatorComponent: Component { activeLineWidth: CGFloat, inactiveLineWidth: CGFloat, isGlassBackground: Bool = false, - backgroundColor: UIColor? = nil, counters: Counters? ) { self.hasUnseen = hasUnseen @@ -40,7 +38,6 @@ public final class AvatarStoryIndicatorComponent: Component { self.activeLineWidth = activeLineWidth self.inactiveLineWidth = inactiveLineWidth self.isGlassBackground = isGlassBackground - self.backgroundColor = backgroundColor self.counters = counters } @@ -63,9 +60,6 @@ public final class AvatarStoryIndicatorComponent: Component { if lhs.isGlassBackground != rhs.isGlassBackground { return false } - if lhs.backgroundColor != rhs.backgroundColor { - return false - } if lhs.counters != rhs.counters { return false } @@ -120,14 +114,6 @@ public final class AvatarStoryIndicatorComponent: Component { var locations: [CGFloat] = [0.0, 1.0] - if let backgroundColor = component.backgroundColor { - context.setLineWidth(lineWidth) - context.setStrokeColor(backgroundColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5).insetBy(dx: lineWidth, dy: lineWidth)) - } - - context.setLineWidth(lineWidth) - if let counters = component.counters, counters.totalCount > 1 { let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5) let radius = (diameter - component.activeLineWidth) * 0.5 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 3ecdf7fef7..f812ce4f84 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -74,6 +74,8 @@ swift_library( "//submodules/StickerPackPreviewUI", "//submodules/Components/AnimatedStickerComponent", "//submodules/OpenInExternalAppUI", + "//submodules/MediaPasteboardUI", + "//submodules/WebPBinding", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 270b41aacd..a498bbfdf8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1803,7 +1803,23 @@ public final class StoryItemSetContainerComponent: Component { self.voiceMessagesRestrictedTooltipController = controller self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut))) }, - paste: { _ in + paste: { [weak self] data in + guard let self else { + return + } + switch data { + case let .images(images): + self.sendMessageContext.presentMediaPasteboard(view: self, subjects: images.map { .image($0) }) + case let .video(data): + let tempFilePath = NSTemporaryDirectory() + "\(Int64.random(in: 0...Int64.max)).mp4" + let url = NSURL(fileURLWithPath: tempFilePath) as URL + try? data.write(to: url) + self.sendMessageContext.presentMediaPasteboard(view: self, subjects: [.video(url)]) + case let .gif(data): + self.sendMessageContext.enqueueGifData(view: self, data: data) + case let .sticker(image, isMemoji): + self.sendMessageContext.enqueueStickerImage(view: self, image: image, isMemoji: isMemoji) + } }, audioRecorder: self.sendMessageContext.audioRecorderValue, videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index d22d9c1fa3..e4d4a736b8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -38,6 +38,8 @@ import TextFieldComponent import StickerPackPreviewUI import OpenInExternalAppUI import SafariServices +import MediaPasteboardUI +import WebPBinding final class StoryItemSetContainerSendMessage { enum InputMode { @@ -525,6 +527,75 @@ final class StoryItemSetContainerSendMessage { controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } + func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) { + guard let component = view.component else { + return + } + let peer = component.slice.peer + let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in + if let self, let view { + let messages = self.transformEnqueueMessages(view: view, messages: [message], silentPosting: false) + self.sendMessages(view: view, peer: peer, messages: messages) + } + }) + } + + func enqueueStickerImage(view: StoryItemSetContainerComponent.View, image: UIImage, isMemoji: Bool) { + guard let component = view.component else { + return + } + let peer = component.slice.peer + + let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0)) + + func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + let format = UIGraphicsImageRendererFormat() + format.scale = 1.0 + let renderer = UIGraphicsImageRenderer(size: size, format: format) + return renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: size)) + } + } else { + return TGScaleImageToPixelSize(image, size) + } + } + + func convertToWebP(image: UIImage, targetSize: CGSize?, targetBoundingSize: CGSize?, quality: CGFloat) -> Signal { + var image = image + if let targetSize = targetSize, let scaledImage = scaleImage(image, size: targetSize, boundiingSize: targetSize) { + image = scaledImage + } + + return Signal { subscriber in + if let data = try? WebP.convert(toWebP: image, quality: quality * 100.0) { + subscriber.putNext(data) + } + subscriber.putCompletion() + + return EmptyDisposable + } |> runOn(Queue.concurrentDefaultQueue()) + } + + let _ = (convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).start(next: { [weak self, weak view] data in + if let self, let view, !data.isEmpty { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + component.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + + var fileAttributes: [TelegramMediaFileAttribute] = [] + fileAttributes.append(.FileName(fileName: "sticker.webp")) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + fileAttributes.append(.ImageSize(size: PixelDimensions(size))) + + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + + let messages = self.transformEnqueueMessages(view: view, messages: [message], silentPosting: false) + self.sendMessages(view: view, peer: peer, messages: messages) + } + }) + } + func setMediaRecordingActive( view: StoryItemSetContainerComponent.View, isActive: Bool, @@ -1763,6 +1834,69 @@ final class StoryItemSetContainerSendMessage { }) } + func presentMediaPasteboard(view: StoryItemSetContainerComponent.View, subjects: [MediaPickerScreen.Subject.Media]) { + guard let component = view.component else { + return + } + let focusedItem = component.slice.item + guard let peerId = focusedItem.peerId else { + return + } + let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { + return + } + + var inputText = NSAttributedString(string: "") + switch inputPanelView.getSendMessageInput() { + case let .text(text): + inputText = text + } + + let peer = component.slice.peer + let theme = defaultDarkPresentationTheme + let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) + let controller = mediaPasteboardScreen( + context: component.context, + updatedPresentationData: updatedPresentationData, + peer: peer, + subjects: subjects, + presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in + if let self { + self.presentMediaPicker( + view: view, + peer: peer, + replyToMessageId: nil, + replyToStoryId: focusedStoryId, + subject: subject, + saveEditedPhotos: saveEditedPhotos, + bannedSendPhotos: bannedSendPhotos, + bannedSendVideos: bannedSendVideos, + present: { controller, mediaPickerContext in + if !inputText.string.isEmpty { + mediaPickerContext?.setCaption(inputText) + } + present(controller, mediaPickerContext) + }, + updateMediaPickerContext: { _ in }, + completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + guard let self, let view else { + return + } + if !inputText.string.isEmpty { + self.clearInputText(view: view) + } + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + } + ) + } + }, + getSourceRect: nil + ) + controller.navigationPresentation = .flatModal + component.controller()?.push(controller) + } + private func enqueueChatContextResult(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, storyId: StoryId?, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { if !canSendMessagesToPeer(peer._asPeer()) { return diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 12da02d6fe..92cb066691 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -454,7 +454,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.playbackStartDisposable.dispose() } - func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme, avatarMaskValue: CGFloat) { + func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) { if let storyData = self.storyData { let avatarStoryView: ComponentView if let current = self.avatarStoryView { @@ -464,8 +464,6 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.avatarStoryView = avatarStoryView } - let inset: CGFloat = storyData.hasUnseen ? 3.0 ? 2.0 - let avatarFrame = self.avatarNode.frame.insetBy(dx: inset, dy: inset) let _ = avatarStoryView.update( transition: Transition(transition), component: AnyComponent(AvatarStoryIndicatorComponent( @@ -474,23 +472,16 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { theme: theme, activeLineWidth: 3.0, inactiveLineWidth: 2.0, - backgroundColor: theme.list.blocksBackgroundColor, counters: nil )), environment: {}, - containerSize: avatarFrame.size + containerSize: self.avatarNode.bounds.size ) if let avatarStoryComponentView = avatarStoryView.view { if avatarStoryComponentView.superview == nil { - self.containerNode.view.addSubview(avatarStoryComponentView) + self.containerNode.view.insertSubview(avatarStoryComponentView, at: 0) } - avatarStoryComponentView.bounds = CGRect(origin: .zero, size: avatarFrame.size) - - let scaleValue = avatarMaskValue * 0.15 - let scale = 1.0 - scaleValue - let offset = min(1.5, avatarMaskValue * 2.5) - avatarStoryComponentView.transform = CGAffineTransformMakeScale(scale, scale) - avatarStoryComponentView.center = avatarFrame.center.offsetBy(dx: 0.0, dy: offset) + avatarStoryComponentView.frame = self.avatarNode.frame } } else { if let avatarStoryView = self.avatarStoryView { @@ -538,17 +529,12 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } var removedPhotoResourceIds = Set() - func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, avatarMaskValue: CGFloat, isSettings: Bool) { + func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { if let peer = peer { let previousItem = self.item var item = item self.item = item - var avatarSize = avatarSize - if self.storyData != nil { - avatarSize = avatarSize - 6.0 - } - var overrideImage: AvatarNodeImageOverride? if peer.isDeleted { overrideImage = .deletedIcon @@ -802,7 +788,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } } - self.updateStoryView(transition: .immediate, theme: theme, avatarMaskValue: avatarMaskValue) + self.updateStoryView(transition: .immediate, theme: theme) } } @@ -1176,7 +1162,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { let isReady = Promise() - var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool, CGFloat)? + var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? var item: PeerInfoAvatarListItem? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? @@ -1247,14 +1233,14 @@ final class PeerInfoAvatarListNode: ASDisplayNode { if let strongSelf = self { strongSelf.item = items.first strongSelf.itemsUpdated?(items) - if let (peer, threadId, threadInfo, theme, avatarSize, isExpanded, avatarMaskValue) = strongSelf.arguments { - strongSelf.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, avatarMaskValue: avatarMaskValue, isSettings: strongSelf.isSettings) + if let (peer, threadId, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments { + strongSelf.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) } } } self.pinchSourceNode.activate = { [weak self] sourceNode in - guard let strongSelf = self, let (_, _, _, _, _, isExpanded, _) = strongSelf.arguments, isExpanded else { + guard let strongSelf = self, let (_, _, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else { return } let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { @@ -1280,13 +1266,13 @@ final class PeerInfoAvatarListNode: ASDisplayNode { } } - func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, isForum: Bool, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, avatarMaskValue: CGFloat, transition: ContainedViewLayoutTransition) { - self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded, avatarMaskValue) + func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, isForum: Bool, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded) self.maskNode.isForum = isForum self.pinchSourceNode.update(size: size, transition: transition) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size) - self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, avatarMaskValue: avatarMaskValue, isSettings: self.isSettings) + self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -3435,7 +3421,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } let avatarMaskValue = max(0.0, min(1.0, contentOffset / 120.0)) - self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, isForum: isForum, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, avatarMaskValue: avatarMaskValue, transition: transition) + self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, isForum: isForum, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition) self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) if additive {