diff --git a/TelegramUI/BotCheckoutControllerNode.swift b/TelegramUI/BotCheckoutControllerNode.swift index b24e7a6ddd..190e42186b 100644 --- a/TelegramUI/BotCheckoutControllerNode.swift +++ b/TelegramUI/BotCheckoutControllerNode.swift @@ -476,9 +476,33 @@ final class BotCheckoutControllerNode: ItemListControllerNode, updatedToken.saveOnServer = false applyPaymentMethod(.webToken(updatedToken)) }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: { - var updatedToken = token - updatedToken.saveOnServer = true - applyPaymentMethod(.webToken(updatedToken)) + guard let strongSelf = self else { + return + } + if paymentForm.passwordMissing { + var updatedToken = token + updatedToken.saveOnServer = false + applyPaymentMethod(.webToken(updatedToken)) + + let controller = SetupTwoStepVerificationController(account: strongSelf.account, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in + if shouldDismiss { + controller.dismiss() + } + switch update { + case .noPassword, .awaitingEmailConfirmation: + break + case .passwordSet: + var updatedToken = token + updatedToken.saveOnServer = true + applyPaymentMethod(.webToken(updatedToken)) + } + }) + strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } else { + var updatedToken = token + updatedToken.saveOnServer = true + applyPaymentMethod(.webToken(updatedToken)) + } })]), nil) } else { var updatedToken = token diff --git a/TelegramUI/BotCheckoutHeaderItem.swift b/TelegramUI/BotCheckoutHeaderItem.swift index 9f41a4d4c7..e60e6ca3c4 100644 --- a/TelegramUI/BotCheckoutHeaderItem.swift +++ b/TelegramUI/BotCheckoutHeaderItem.swift @@ -151,7 +151,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { var imageApply: (() -> Void)? var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? if let photo = item.invoice.photo, let dimensions = photo.dimensions { - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) imageApply = makeImageLayout(arguments) maxTextWidth = max(1.0, maxTextWidth - imageSize.width - imageTextSpacing) if imageUpdated { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 25cb53db12..e7979b0be2 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -4799,7 +4799,32 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID disposable = MetaDisposable() strongSelf.resolveUrlDisposable = disposable } + var cancelImpl: (() -> Void)? + let presentationData = strongSelf.presentationData + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + cancelImpl = { [weak self] in + self?.resolveUrlDisposable?.set(nil) + } disposable.set((resolveUrl(account: strongSelf.account, url: url) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { strongSelf.openResolved(result) diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index dbfaa0f6cd..581360c5ee 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -486,6 +486,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { } else { transition.animatePositionAdditive(layer: scrubberView.layer, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight)) } + scrubberView.alpha = 1.0 scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight)) @@ -507,6 +508,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { } else { transition.updateFrame(view: scrubberView, frame: scrubberView.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight)) } + scrubberView.alpha = 0.0 scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) } transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight)) diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 8ee4460ad1..bdd9ed081e 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -584,7 +584,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var imageApply: (() -> Void)? if let inlineImageSize = inlineImageSize, let inlineImageDimensions = inlineImageDimensions { let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: inlineImageDimensions.aspectFilled(inlineImageSize), boundingSize: inlineImageSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: inlineImageDimensions.aspectFilled(inlineImageSize), boundingSize: inlineImageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: incoming ? presentationData.theme.theme.chat.bubble.incomingMediaPlaceholderColor : presentationData.theme.theme.chat.bubble.outgoingMediaPlaceholderColor) imageApply = imageLayout(arguments) } diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index a695098d5e..daf79b4a0e 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -510,7 +510,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { iconNode = TransformImageNode() strongSelf.iconNode = iconNode strongSelf.insertSubnode(iconNode, at: 0) - let arguments = TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: CGSize(width: 74.0, height: 74.0), boundingSize: CGSize(width: 74.0, height: 74.0), intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: CGSize(width: 74.0, height: 74.0), boundingSize: CGSize(width: 74.0, height: 74.0), intrinsicInsets: UIEdgeInsets(), emptyColor: incoming ? bubbleTheme.incomingMediaPlaceholderColor : bubbleTheme.outgoingMediaPlaceholderColor) let apply = iconNode.asyncLayout()(arguments) apply() } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index b29590932c..d5b9dbabe8 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -381,7 +381,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } - let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground) + let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: message.effectivelyIncoming(account.peerId) ? theme.chat.bubble.incomingMediaPlaceholderColor : theme.chat.bubble.outgoingMediaPlaceholderColor) let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize) diff --git a/TelegramUI/ChatMessageMapBubbleContentNode.swift b/TelegramUI/ChatMessageMapBubbleContentNode.swift index d61b51fa33..deadb0b429 100644 --- a/TelegramUI/ChatMessageMapBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMapBubbleContentNode.swift @@ -239,7 +239,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } return (contentWidth, { boundingWidth in - let arguments = TransformImageArguments(corners: imageCorners, imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingMediaPlaceholderColor : item.presentationData.theme.theme.chat.bubble.outgoingMediaPlaceholderColor) let imageLayoutSize = CGSize(width: imageSize.width + bubbleInsets.left + bubbleInsets.right, height: imageSize.height + bubbleInsets.top + bubbleInsets.bottom) diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index 32a82af8ae..15f65c2114 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -50,6 +50,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let titleString = message.author?.displayTitle ?? "" let (textString, isMedia) = descriptionStringForMessage(message, strings: strings, accountPeerId: account.peerId) + let placeholderColor: UIColor = message.effectivelyIncoming(account.peerId) ? theme.chat.bubble.incomingMediaPlaceholderColor : theme.chat.bubble.outgoingMediaPlaceholderColor let titleColor: UIColor let lineImage: UIImage? let textColor: UIColor @@ -110,7 +111,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { imageSize.width += 2.0 imageSize.height += 2.0 } - applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: placeholderColor)) } var mediaUpdated = false diff --git a/TelegramUI/DefaultDarkAccentPresentationTheme.swift b/TelegramUI/DefaultDarkAccentPresentationTheme.swift index ff0e5d1097..16ba3932e1 100644 --- a/TelegramUI/DefaultDarkAccentPresentationTheme.swift +++ b/TelegramUI/DefaultDarkAccentPresentationTheme.swift @@ -100,7 +100,8 @@ private let list = PresentationThemeList( placeholderColor: UIColor(rgb: 0x4d4d4d), primaryColor: .white, controlColor: UIColor(rgb: 0x4d4d4d) - ) + ), + mediaPlaceholderColor: UIColor(rgb: 0x1e2c3a) ) private let chatList = PresentationThemeChatList( @@ -182,7 +183,9 @@ private let bubble = PresentationThemeChatBubble( selectionControlForegroundColor: .white, mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), deliveryFailedFillColor: destructiveColor, - deliveryFailedForegroundColor: .white + deliveryFailedForegroundColor: .white, + incomingMediaPlaceholderColor: UIColor(rgb: 0x1e2c3a), + outgoingMediaPlaceholderColor: UIColor(rgb: 0x2d5883) ) private let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index c605c8f9cb..22c7129cbf 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -100,7 +100,8 @@ private let list = PresentationThemeList( placeholderColor: UIColor(rgb: 0x4d4d4d), primaryColor: .white, controlColor: UIColor(rgb: 0x4d4d4d) - ) + ), + mediaPlaceholderColor: UIColor(rgb: 0x1c1c1d) ) private let chatList = PresentationThemeChatList( @@ -182,7 +183,9 @@ private let bubble = PresentationThemeChatBubble( selectionControlForegroundColor: .black, mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), deliveryFailedFillColor: destructiveColor, - deliveryFailedForegroundColor: .white + deliveryFailedForegroundColor: .white, + incomingMediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(.white, alpha: 0.05), + outgoingMediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(.white, alpha: 0.05) ) private let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 5ed44b19c8..39628d4fc6 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -100,7 +100,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr placeholderColor: UIColor(rgb: 0x96979d), primaryColor: .black, controlColor: UIColor(rgb: 0x96979d) - ) + ), + mediaPlaceholderColor: UIColor(rgb: 0xe4e4e4) ) let chatList = PresentationThemeChatList( @@ -212,7 +213,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr selectionControlForegroundColor: .white, mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), deliveryFailedFillColor: destructiveColor, - deliveryFailedForegroundColor: .white + deliveryFailedForegroundColor: .white, + incomingMediaPlaceholderColor: UIColor(rgb: 0xe8ecf0), + outgoingMediaPlaceholderColor: UIColor(rgb: 0xd2f2b6) ) let bubbleDay = PresentationThemeChatBubble( @@ -264,7 +267,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr selectionControlForegroundColor: .white, mediaHighlightOverlayColor: UIColor(rgb: 0xffffff, alpha: 0.6), deliveryFailedFillColor: destructiveColor, - deliveryFailedForegroundColor: .white + deliveryFailedForegroundColor: .white, + incomingMediaPlaceholderColor: UIColor(rgb: 0xffffff).withMultipliedBrightnessBy(0.95), + outgoingMediaPlaceholderColor: accentColor.withMultipliedBrightnessBy(0.95) ) let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/GalleryPagerNode.swift b/TelegramUI/GalleryPagerNode.swift index e83e49fcea..ba932819d4 100644 --- a/TelegramUI/GalleryPagerNode.swift +++ b/TelegramUI/GalleryPagerNode.swift @@ -259,7 +259,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { self.itemNodes.remove(at: internalIndex) } - private func updateItemNodes(transition: ContainedViewLayoutTransition, forceOffsetReset: Bool = false) { + private func updateItemNodes(transition: ContainedViewLayoutTransition, forceOffsetReset: Bool = false, forceLoad: Bool = false) { if self.items.isEmpty || self.containerLayout == nil { return } @@ -285,7 +285,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { if let centralItemIndex = self.centralItemIndex, let centralItemNode = self.visibleItemNode(at: centralItemIndex) { if centralItemIndex != 0 { - if self.visibleItemNode(at: centralItemIndex - 1) == nil { + if self.shouldLoadItems(force: forceLoad) && self.visibleItemNode(at: centralItemIndex - 1) == nil { let node = self.makeNodeForItem(at: centralItemIndex - 1) node.frame = centralItemNode.frame.offsetBy(dx: -centralItemNode.frame.size.width - self.pageGap, dy: 0.0) if let containerLayout = self.containerLayout { @@ -296,7 +296,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { } if centralItemIndex != items.count - 1 { - if self.visibleItemNode(at: centralItemIndex + 1) == nil { + if self.shouldLoadItems(force: forceLoad) && self.visibleItemNode(at: centralItemIndex + 1) == nil { let node = self.makeNodeForItem(at: centralItemIndex + 1) node.frame = centralItemNode.frame.offsetBy(dx: centralItemNode.frame.size.width + self.pageGap, dy: 0.0) if let containerLayout = self.containerLayout { @@ -314,7 +314,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { self.scrollView.contentOffset = CGPoint(x: centralItemNode.frame.minX - self.pageGap, y: 0.0) } - if let centralItemCandidateNode = self.centralItemCandidate(), centralItemCandidateNode.index != centralItemIndex { + if self.shouldLoadItems(force: forceLoad), let centralItemCandidateNode = self.centralItemCandidate(), centralItemCandidateNode.index != centralItemIndex { for i in (0 ..< self.itemNodes.count).reversed() { let node = self.itemNodes[i] if node.index < centralItemCandidateNode.index - 1 || node.index > centralItemCandidateNode.index + 1 { @@ -328,7 +328,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { notifyCentralItemUpdated = true if centralItemCandidateNode.index != 0 { - if self.visibleItemNode(at: centralItemCandidateNode.index - 1) == nil { + if self.shouldLoadItems(force: forceLoad) && self.visibleItemNode(at: centralItemCandidateNode.index - 1) == nil { let node = self.makeNodeForItem(at: centralItemCandidateNode.index - 1) node.frame = centralItemCandidateNode.frame.offsetBy(dx: -centralItemCandidateNode.frame.size.width - self.pageGap, dy: 0.0) if let containerLayout = self.containerLayout { @@ -339,7 +339,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { } if centralItemCandidateNode.index != items.count - 1 { - if self.visibleItemNode(at: centralItemCandidateNode.index + 1) == nil { + if self.shouldLoadItems(force: forceLoad) && self.visibleItemNode(at: centralItemCandidateNode.index + 1) == nil { let node = self.makeNodeForItem(at: centralItemCandidateNode.index + 1) node.frame = centralItemCandidateNode.frame.offsetBy(dx: centralItemCandidateNode.frame.size.width + self.pageGap, dy: 0.0) if let containerLayout = self.containerLayout { @@ -383,6 +383,28 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { } } + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.ensureItemsLoaded(force: false) + } + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.ensureItemsLoaded(force: true) + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.ensureItemsLoaded(force: true) + } + + private func shouldLoadItems(force: Bool) -> Bool { + return force || (!self.scrollView.isDecelerating && !self.scrollView.isDragging) + } + + private func ensureItemsLoaded(force: Bool) { + self.updateItemNodes(transition: .immediate, forceLoad: force) + } + private func centralItemCandidate() -> GalleryItemNode? { let hotizontlOffset = self.scrollView.contentOffset.x + self.pageGap var closestNodeAndDistance: (Int, CGFloat)? diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index f5b38ea6a3..52c10827ed 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -303,9 +303,9 @@ final class GridMessageItemNode: GridItemNode { let imageFrame = self.bounds.insetBy(dx: 1.0, dy: 1.0) self.imageNode.frame = imageFrame - if let (_, _, mediaDimensions) = self.currentState { + if let item = self.item, let (_, _, mediaDimensions) = self.currentState { let imageSize = mediaDimensions.aspectFilled(imageFrame.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))() } self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index 9603c02aa2..892a2c22bf 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -443,12 +443,12 @@ final class ListMessageFileItemNode: ListMessageNode { case let .imageRepresentation(_, representation): let iconSize = CGSize(width: 42.0, height: 42.0) let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case .albumArt: let iconSize = CGSize(width: 46.0, height: 46.0) let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } } diff --git a/TelegramUI/ListMessageSnippetItemNode.swift b/TelegramUI/ListMessageSnippetItemNode.swift index 1e196e9afd..cb1f237192 100644 --- a/TelegramUI/ListMessageSnippetItemNode.swift +++ b/TelegramUI/ListMessageSnippetItemNode.swift @@ -298,7 +298,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation { let iconSize = CGSize(width: 42.0, height: 42.0) let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } diff --git a/TelegramUI/NativeVideoContent.swift b/TelegramUI/NativeVideoContent.swift index 2716049829..d889c5db50 100644 --- a/TelegramUI/NativeVideoContent.swift +++ b/TelegramUI/NativeVideoContent.swift @@ -47,8 +47,9 @@ final class NativeVideoContent: UniversalVideoContent { let enableSound: Bool let baseRate: Double let fetchAutomatically: Bool + let placeholderColor: UIColor - init(id: NativeVideoContentId, fileReference: FileMediaReference, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true) { + init(id: NativeVideoContentId, fileReference: FileMediaReference, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, placeholderColor: UIColor = .white) { self.id = id self.nativeId = id self.fileReference = fileReference @@ -59,10 +60,11 @@ final class NativeVideoContent: UniversalVideoContent { self.enableSound = enableSound self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically + self.placeholderColor = placeholderColor } func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, placeholderColor: self.placeholderColor) } func isEqual(to other: UniversalVideoContent) -> Bool { @@ -87,6 +89,8 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private let playerNode: MediaPlayerNode private let playbackCompletedListeners = Bag<() -> Void>() + private let placeholderColor: UIColor + private var initializedStatus = false private let _status = Promise() var status: Signal { @@ -110,9 +114,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var validLayout: CGSize? - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, placeholderColor: UIColor) { self.postbox = postbox self.fileReference = fileReference + self.placeholderColor = placeholderColor self.imageNode = TransformImageNode() @@ -190,7 +195,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent if let dimensions = self.dimensions { let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) let makeLayout = self.imageNode.asyncLayout() - let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.fileReference.media.isInstantVideo ? .clear : .white)) + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.fileReference.media.isInstantVideo ? .clear : self.placeholderColor)) applyLayout() } diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 4360ef6862..189d453cb1 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -271,8 +271,8 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef } } } - } |> filter({ - return $0.0 != nil || $0.1 != nil || $0.2 + } |> filter({ _ in + return true//$0.0 != nil || $0.1 != nil || $0.2 }) return signal @@ -633,32 +633,69 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE if thumbnailImage == nil && fullSizeImage == nil { let color = arguments.emptyColor ?? UIColor.white c.setFillColor(color.cgColor) - c.fill(fittedRect) + c.fill(drawingRect) } else { if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { let blurSourceImage = thumbnailImage ?? fullSizeImage if let fullSizeImage = blurSourceImage { let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - if let blurredImage = thumbnailContext.generateImage() { + var sideBlurredImage: UIImage? + if true { + let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 100.0, height: 100.0)) + + let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + telegramFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + sideBlurredImage = additionalBlurContext.generateImage() + } else { + sideBlurredImage = thumbnailContext.generateImage() + } + } else { + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + sideBlurredImage = thumbnailContext.generateImage() + } + + + if let blurredImage = sideBlurredImage { let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) c.interpolationQuality = .medium c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x:arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) c.setBlendMode(.normal) - c.setFillColor(UIColor(white: 1.0, alpha: 0.5).cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).withAlphaComponent(0.05).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.copy) } } else { + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) } } @@ -797,7 +834,7 @@ public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMed context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) } @@ -890,7 +927,7 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) } @@ -991,7 +1028,7 @@ func chatSecretPhoto(account: Account, photoReference: ImageMediaReference) -> S context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) } @@ -1167,7 +1204,8 @@ func avatarGalleryThumbnailPhoto(account: Account, representations: [(TelegramMe func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaReference) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: photoReference, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal + |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1222,6 +1260,7 @@ func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaReference context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) } @@ -1439,7 +1478,7 @@ func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaRe c.interpolationQuality = .medium c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) c.setBlendMode(.normal) - c.setFillColor(UIColor(white: 1.0, alpha: 0.5).cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).withAlphaComponent(0.5).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.copy) } @@ -1447,7 +1486,7 @@ func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaRe c.fill(arguments.drawingRect) } case let .fill(color): - c.setFillColor(color.cgColor) + c.setFillColor((arguments.emptyColor ?? color).cgColor) c.fill(arguments.drawingRect) } } @@ -2134,7 +2173,7 @@ func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) } else { context.withFlippedContext { c in c.setBlendMode(.copy) - c.setFillColor(UIColor.white.cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.normal) @@ -2197,7 +2236,7 @@ func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Signal<(T } else { context.withFlippedContext { c in c.setBlendMode(.copy) - c.setFillColor(UIColor.white.cgColor) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.normal) diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index 1f9d7edeed..cd69d07289 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -296,8 +296,9 @@ public final class PresentationThemeList { public let itemCheckColors: PresentationThemeCheck public let controlSecondaryColor: UIColor public let freeInputField: PresentationInputFieldTheme + public let mediaPlaceholderColor: UIColor - public init(blocksBackgroundColor: UIColor, plainBackgroundColor: UIColor, itemPrimaryTextColor: UIColor, itemSecondaryTextColor: UIColor, itemDisabledTextColor: UIColor, itemAccentColor: UIColor, itemHighlightedColor: UIColor, itemDestructiveColor: UIColor, itemPlaceholderTextColor: UIColor, itemBlocksBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemBlocksSeparatorColor: UIColor, itemPlainSeparatorColor: UIColor, disclosureArrowColor: UIColor, sectionHeaderTextColor: UIColor, freeTextColor: UIColor, freeTextErrorColor: UIColor, freeTextSuccessColor: UIColor, freeMonoIcon: UIColor, itemSwitchColors: PresentationThemeSwitch, itemDisclosureActions: PresentationThemeItemDisclosureActions, itemCheckColors: PresentationThemeCheck, controlSecondaryColor: UIColor, freeInputField: PresentationInputFieldTheme) { + public init(blocksBackgroundColor: UIColor, plainBackgroundColor: UIColor, itemPrimaryTextColor: UIColor, itemSecondaryTextColor: UIColor, itemDisabledTextColor: UIColor, itemAccentColor: UIColor, itemHighlightedColor: UIColor, itemDestructiveColor: UIColor, itemPlaceholderTextColor: UIColor, itemBlocksBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemBlocksSeparatorColor: UIColor, itemPlainSeparatorColor: UIColor, disclosureArrowColor: UIColor, sectionHeaderTextColor: UIColor, freeTextColor: UIColor, freeTextErrorColor: UIColor, freeTextSuccessColor: UIColor, freeMonoIcon: UIColor, itemSwitchColors: PresentationThemeSwitch, itemDisclosureActions: PresentationThemeItemDisclosureActions, itemCheckColors: PresentationThemeCheck, controlSecondaryColor: UIColor, freeInputField: PresentationInputFieldTheme, mediaPlaceholderColor: UIColor) { self.blocksBackgroundColor = blocksBackgroundColor self.plainBackgroundColor = plainBackgroundColor self.itemPrimaryTextColor = itemPrimaryTextColor @@ -322,6 +323,7 @@ public final class PresentationThemeList { self.itemCheckColors = itemCheckColors self.controlSecondaryColor = controlSecondaryColor self.freeInputField = freeInputField + self.mediaPlaceholderColor = mediaPlaceholderColor } } @@ -488,7 +490,10 @@ public final class PresentationThemeChatBubble { public let deliveryFailedFillColor: UIColor public let deliveryFailedForegroundColor: UIColor - public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor, deliveryFailedFillColor: UIColor, deliveryFailedForegroundColor: UIColor) { + public let incomingMediaPlaceholderColor: UIColor + public let outgoingMediaPlaceholderColor: UIColor + + public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor, deliveryFailedFillColor: UIColor, deliveryFailedForegroundColor: UIColor, incomingMediaPlaceholderColor: UIColor, outgoingMediaPlaceholderColor: UIColor) { self.incoming = incoming self.outgoing = outgoing self.freeform = freeform @@ -550,6 +555,9 @@ public final class PresentationThemeChatBubble { self.deliveryFailedFillColor = deliveryFailedFillColor self.deliveryFailedForegroundColor = deliveryFailedForegroundColor + + self.incomingMediaPlaceholderColor = incomingMediaPlaceholderColor + self.outgoingMediaPlaceholderColor = outgoingMediaPlaceholderColor } } diff --git a/TelegramUI/SecureIdPlaintextFormControllerNode.swift b/TelegramUI/SecureIdPlaintextFormControllerNode.swift index 0068549d96..24df09a321 100644 --- a/TelegramUI/SecureIdPlaintextFormControllerNode.swift +++ b/TelegramUI/SecureIdPlaintextFormControllerNode.swift @@ -888,42 +888,55 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - guard var innerState = strongSelf.innerState else { - return - } - guard case .saving = innerState.actionState else { - return - } - innerState.actionState = .none - innerState.data = .email(.verify(EmailVerifyState(email: value, payload: result, code: ""))) - strongSelf.updateInnerState(transition: .immediate, with: innerState) - strongSelf.activateMainInput() + self.actionDisposable.set((saveSecureIdValue(postbox: self.account.postbox, network: self.account.network, context: self.context, value: .email(SecureIdEmailValue(email: value)), uploadedFiles: [:]) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let strongSelf = self else { + return + } + strongSelf.completedWithValue?(result) + }, error: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.actionDisposable.set((secureIdPrepareEmailVerification(network: strongSelf.account.network, value: SecureIdEmailValue(email: value)) + |> deliverOnMainQueue).start(next: { result in + guard let strongSelf = self else { + return } - }, error: { [weak self] error in - if let strongSelf = self { - guard var innerState = strongSelf.innerState else { - return - } - guard case .saving = innerState.actionState else { - return - } - innerState.actionState = .none - strongSelf.updateInnerState(transition: .immediate, with: innerState) - let errorText: String - switch error { - case .generic: - errorText = strongSelf.strings.Login_UnknownError - case .invalidEmail: - errorText = strongSelf.strings.TwoStepAuth_EmailInvalid - case .flood: - errorText = strongSelf.strings.Login_CodeFloodError - } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_OK, action: {})]), nil) - } + guard var innerState = strongSelf.innerState else { + return + } + guard case .saving = innerState.actionState else { + return + } + innerState.actionState = .none + innerState.data = .email(.verify(EmailVerifyState(email: value, payload: result, code: ""))) + strongSelf.updateInnerState(transition: .immediate, with: innerState) + strongSelf.activateMainInput() + }, error: { [weak self] error in + guard let strongSelf = self else { + return + } + guard var innerState = strongSelf.innerState else { + return + } + guard case .saving = innerState.actionState else { + return + } + innerState.actionState = .none + strongSelf.updateInnerState(transition: .immediate, with: innerState) + let errorText: String + switch error { + case .generic: + errorText = strongSelf.strings.Login_UnknownError + case .invalidEmail: + errorText = strongSelf.strings.TwoStepAuth_EmailInvalid + case .flood: + errorText = strongSelf.strings.Login_CodeFloodError + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.theme), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_OK, action: {})]), nil) })) + })) } } diff --git a/TelegramUI/SecureIdValueFormFileItem.swift b/TelegramUI/SecureIdValueFormFileItem.swift index 981b710ead..598bb918d9 100644 --- a/TelegramUI/SecureIdValueFormFileItem.swift +++ b/TelegramUI/SecureIdValueFormFileItem.swift @@ -135,7 +135,7 @@ final class SecureIdValueFormFileItemNode: FormBlockItemNode Void private var innerState: SetupTwoStepVerificationControllerInnerState + private let activityIndicator: ActivityIndicator private var contentNode: SetupTwoStepVerificationContentNode? private let actionDisposable = MetaDisposable() @@ -152,11 +156,34 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { self.dismiss = dismiss self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.innerState = SetupTwoStepVerificationControllerInnerState(layout: nil, data: SetupTwoStepVerificationControllerDataState(activity: false, state: SetupTwoStepVerificationState(initialState: initialState))) + self.activityIndicator = ActivityIndicator(type: .custom(self.presentationData.theme.list.itemAccentColor, 22.0, 2.0, false)) super.init() self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.processStateUpdated() + + if self.innerState.data.state == nil { + self.actionDisposable.set((twoStepAuthData(account.network) + |> deliverOnMainQueue).start(next: { [weak self] data in + guard let strongSelf = self else { + return + } + if data.currentPasswordDerivation != nil { + strongSelf.stateUpdated(.passwordSet(password: nil, hasRecoveryEmail: data.hasRecovery, hasSecureValues: data.hasSecretValues), true) + } else { + strongSelf.updateState({ state in + var state = state + if let unconfirmedEmailPattern = data.unconfirmedEmailPattern { + state.data.state = .confirmEmail(state: .confirm(password: nil, hasSecureValues: data.hasSecretValues, pattern: unconfirmedEmailPattern, codeLength: nil), pattern: unconfirmedEmailPattern, codeLength: nil, code: "") + } else { + state.data.state = .enterPassword(mode: .create, password: "") + } + return state + }, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + })) + } } deinit { @@ -194,8 +221,8 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { let nextAction: SetupTwoStepVerificationNextAction if self.innerState.data.activity { nextAction = .activity - } else { - switch self.innerState.data.state { + } else if let state = self.innerState.data.state { + switch state { case let .enterPassword(_, password): nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !password.isEmpty) case let .confirmPassword(_, _, confirmation): @@ -214,6 +241,8 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { case let .confirmEmail(_, _, _, code): nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !code.isEmpty) } + } else { + nextAction = .none } self.updateBackAction(backAction) self.updateNextAction(nextAction) @@ -225,6 +254,8 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { state.layout = SetupTwoStepVerificationControllerLayoutState(layout: layout, navigationHeight: navigationBarHeight) return state }, transition: transition) + let indicatorSize = CGSize(width: 22.0, height: 22.0) + self.activityIndicator.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: floor((layout.size.height - indicatorSize.height) / 2.0)), size: indicatorSize) } private func transition(state: SetupTwoStepVerificationControllerState, transition: ContainedViewLayoutTransition) { @@ -238,191 +269,209 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { } } let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height)) - if state.data.state.kind != self.contentNode?.kind { - let title: String - let subtitle: String - let inputType: SetupTwoStepVerificationInputType - let inputPlaceholder: String - let inputText: String - let isPassword: Bool - var leftAction: SetupTwoStepVerificationContentAction? - var rightAction: SetupTwoStepVerificationContentAction? - switch state.data.state { - case let .enterPassword(mode, password): - switch mode { - case .create: - title = "Create a Password" - subtitle = "Please create a password which will be used to protect your data." - case .update: - title = "Change Password" - subtitle = "Please enter a new password which will be used to protect your data." - } - inputType = .password - inputPlaceholder = "Password" - inputText = password - isPassword = true - case let .confirmPassword(_, _, confirmation): - title = "Re-enter your Password" - subtitle = "Please confirm your password." - inputType = .password - inputPlaceholder = "Password" - inputText = confirmation - isPassword = true - case let .enterHint(_, _, hint): - title = "Add a Hint" - subtitle = "You can create an optional hint for your password." - inputType = .text - inputPlaceholder = "Hint" - inputText = hint - isPassword = false - case let .enterEmail(enterState, email): - title = "Recovery Email" - switch enterState { - case let .add(hadRecoveryEmail, _, _) where hadRecoveryEmail: - subtitle = "Please enter your new recovery email. It is the only way to recover a forgotten password." - default: - subtitle = "Please add your valid e-mail. It is the only way to recover a forgotten password." - } - inputType = .email - inputPlaceholder = "Email" - inputText = email - isPassword = false - case let .confirmEmail(confirmState, _, _, code): - title = "Recovery Email" - let emailPattern: String - switch confirmState { - case let .create(password, hint, email): - emailPattern = email - leftAction = SetupTwoStepVerificationContentAction(title: "Change E-Mail", action: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.updateState({ state in - var state = state - state.data.activity = true - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.account.network, currentPassword: nil, updatedPassword: .none) - |> deliverOnMainQueue).start(next: { _ in - guard let strongSelf = self else { - return - } - strongSelf.updateState({ state in - var state = state - state.data.activity = false - state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "") - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - strongSelf.stateUpdated(.noPassword, false) - }, error: { _ in - guard let strongSelf = self else { - return - } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) - strongSelf.updateState({ state in - var state = state - state.data.activity = false - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - })) - }) - case let .add(password, hadRecoveryEmail, hasSecureValues, email): - emailPattern = email - leftAction = SetupTwoStepVerificationContentAction(title: "Change E-Mail", action: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.updateState({ state in - var state = state - state.data.state = .enterEmail(state: .add(hadRecoveryEmail: hadRecoveryEmail, hasSecureValues: hasSecureValues, password: password), email: "") - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - }) - case let .confirm(_, _, pattern, _): - emailPattern = pattern - } - subtitle = "Please enter the code we've just emailed at \(emailPattern)." - inputType = .code - inputPlaceholder = "Code" - inputText = code - isPassword = true - rightAction = SetupTwoStepVerificationContentAction(title: "Resend Code", action: { [weak self] in - guard let strongSelf = self else { - return + if state.data.state?.kind != self.contentNode?.kind { + if let dataState = state.data.state { + let title: String + let subtitle: String + let inputType: SetupTwoStepVerificationInputType + let inputPlaceholder: String + let inputText: String + let isPassword: Bool + var leftAction: SetupTwoStepVerificationContentAction? + var rightAction: SetupTwoStepVerificationContentAction? + switch dataState { + case let .enterPassword(mode, password): + switch mode { + case .create: + title = "Create a Password" + subtitle = "Please create a password which will be used to protect your data." + case .update: + title = "Change Password" + subtitle = "Please enter a new password which will be used to protect your data." } - strongSelf.updateState({ state in - var state = state - state.data.activity = true - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - strongSelf.actionDisposable.set((resendTwoStepRecoveryEmail(network: strongSelf.account.network) - |> deliverOnMainQueue).start(error: { error in - guard let strongSelf = self else { - return - } - let text: String - switch error { - case .flood: - text = strongSelf.presentationData.strings.TwoStepAuth_FloodError - case .generic: - text = strongSelf.presentationData.strings.Login_UnknownError - } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) - strongSelf.updateState({ state in - var state = state - state.data.activity = false - return state - }, transition: .animated(duration: 0.5, curve: .spring)) - }, completed: { + inputType = .password + inputPlaceholder = "Password" + inputText = password + isPassword = true + case let .confirmPassword(_, _, confirmation): + title = "Re-enter your Password" + subtitle = "Please confirm your password." + inputType = .password + inputPlaceholder = "Password" + inputText = confirmation + isPassword = true + case let .enterHint(_, _, hint): + title = "Add a Hint" + subtitle = "You can create an optional hint for your password." + inputType = .text + inputPlaceholder = "Hint" + inputText = hint + isPassword = false + case let .enterEmail(enterState, email): + title = "Recovery Email" + switch enterState { + case let .add(hadRecoveryEmail, _, _) where hadRecoveryEmail: + subtitle = "Please enter your new recovery email. It is the only way to recover a forgotten password." + default: + subtitle = "Please add your valid e-mail. It is the only way to recover a forgotten password." + } + inputType = .email + inputPlaceholder = "Email" + inputText = email + isPassword = false + case let .confirmEmail(confirmState, _, _, code): + title = "Recovery Email" + let emailPattern: String + switch confirmState { + case let .create(password, hint, email): + emailPattern = email + leftAction = SetupTwoStepVerificationContentAction(title: "Change E-Mail", action: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.updateState({ state in + var state = state + state.data.activity = true + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.account.network, currentPassword: nil, updatedPassword: .none) + |> deliverOnMainQueue).start(next: { _ in + guard let strongSelf = self else { + return + } + strongSelf.updateState({ state in + var state = state + state.data.activity = false + state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "") + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + strongSelf.stateUpdated(.noPassword, false) + }, error: { _ in + guard let strongSelf = self else { + return + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) + strongSelf.updateState({ state in + var state = state + state.data.activity = false + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + })) + }) + case let .add(password, hadRecoveryEmail, hasSecureValues, email): + emailPattern = email + leftAction = SetupTwoStepVerificationContentAction(title: "Change E-Mail", action: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.updateState({ state in + var state = state + state.data.state = .enterEmail(state: .add(hadRecoveryEmail: hadRecoveryEmail, hasSecureValues: hasSecureValues, password: password), email: "") + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + }) + case let .confirm(_, _, pattern, _): + emailPattern = pattern + } + subtitle = "Please enter the code we've just emailed at \(emailPattern)." + inputType = .code + inputPlaceholder = "Code" + inputText = code + isPassword = true + rightAction = SetupTwoStepVerificationContentAction(title: "Resend Code", action: { [weak self] in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state - state.data.activity = false + state.data.activity = true return state }, transition: .animated(duration: 0.5, curve: .spring)) - })) + strongSelf.actionDisposable.set((resendTwoStepRecoveryEmail(network: strongSelf.account.network) + |> deliverOnMainQueue).start(error: { error in + guard let strongSelf = self else { + return + } + let text: String + switch error { + case .flood: + text = strongSelf.presentationData.strings.TwoStepAuth_FloodError + case .generic: + text = strongSelf.presentationData.strings.Login_UnknownError + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) + strongSelf.updateState({ state in + var state = state + state.data.activity = false + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + }, completed: { + guard let strongSelf = self else { + return + } + strongSelf.updateState({ state in + var state = state + state.data.activity = false + return state + }, transition: .animated(duration: 0.5, curve: .spring)) + })) + }) + } + let contentNode = SetupTwoStepVerificationContentNode(theme: self.presentationData.theme, kind: dataState.kind, title: title, subtitle: subtitle, inputType: inputType, placeholder: inputPlaceholder, text: inputText, isPassword: isPassword, textUpdated: { [weak self] text in + guard let strongSelf = self else { + return + } + var inplicitelyActivateNextAction = false + if case let .confirmEmail(confirmEmail)? = strongSelf.innerState.data.state, let codeLength = confirmEmail.codeLength, confirmEmail.code.count != codeLength, text.count == codeLength { + inplicitelyActivateNextAction = true + } + strongSelf.updateState({ state in + var state = state + state.data.state?.updateInputText(text) + return state + }, transition: .immediate) + if inplicitelyActivateNextAction { + strongSelf.activateNextAction() + } + }, returnPressed: { [weak self] in + self?.activateNextAction() + }, leftAction: leftAction, rightAction: rightAction) + self.insertSubnode(contentNode, at: 0) + contentNode.updateIsEnabled(!state.data.activity) + contentNode.updateLayout(size: contentFrame.size, insets: insets, visibleInsets: visibleInsets, transition: .immediate) + contentNode.frame = contentFrame + contentNode.activate() + if let currentContentNode = self.contentNode { + if currentContentNode.kind.rawValue < contentNode.kind.rawValue { + transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in + currentContentNode?.removeFromSupernode() + }) + transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width) + } else { + transition.updatePosition(node: currentContentNode, position: CGPoint(x: contentFrame.size.width + contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in + currentContentNode?.removeFromSupernode() + }) + transition.animateHorizontalOffsetAdditive(node: contentNode, offset: contentFrame.width) + } + } else if transition.isAnimated { + contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + if self.activityIndicator.supernode != nil { + transition.updateAlpha(node: self.activityIndicator, alpha: 0.0, completion: { [weak self] _ in + self?.activityIndicator.removeFromSupernode() }) + } + self.contentNode = contentNode + } else if let currentContentNode = self.contentNode { + transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in + currentContentNode?.removeFromSupernode() + }) + if self.activityIndicator.supernode == nil { + self.addSubnode(self.activityIndicator) + transition.updateAlpha(node: self.activityIndicator, alpha: 1.0) + } + self.contentNode = nil } - let contentNode = SetupTwoStepVerificationContentNode(theme: self.presentationData.theme, kind: state.data.state.kind, title: title, subtitle: subtitle, inputType: inputType, placeholder: inputPlaceholder, text: inputText, isPassword: isPassword, textUpdated: { [weak self] text in - guard let strongSelf = self else { - return - } - var inplicitelyActivateNextAction = false - if case let .confirmEmail(confirmEmail) = strongSelf.innerState.data.state, let codeLength = confirmEmail.codeLength, confirmEmail.code.count != codeLength, text.count == codeLength { - inplicitelyActivateNextAction = true - } - strongSelf.updateState({ state in - var state = state - state.data.state.updateInputText(text) - return state - }, transition: .immediate) - if inplicitelyActivateNextAction { - strongSelf.activateNextAction() - } - }, returnPressed: { [weak self] in - self?.activateNextAction() - }, leftAction: leftAction, rightAction: rightAction) - self.insertSubnode(contentNode, at: 0) - contentNode.updateIsEnabled(!state.data.activity) - contentNode.updateLayout(size: contentFrame.size, insets: insets, visibleInsets: visibleInsets, transition: .immediate) - contentNode.frame = contentFrame - contentNode.activate() - if let currentContentNode = self.contentNode { - if currentContentNode.kind.rawValue < contentNode.kind.rawValue { - transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in - currentContentNode?.removeFromSupernode() - }) - transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width) - } else { - transition.updatePosition(node: currentContentNode, position: CGPoint(x: contentFrame.size.width + contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in - currentContentNode?.removeFromSupernode() - }) - transition.animateHorizontalOffsetAdditive(node: contentNode, offset: contentFrame.width) - } - } - self.contentNode = contentNode } else if let contentNode = self.contentNode { contentNode.updateIsEnabled(!state.data.activity) transition.updateFrame(node: contentNode, frame: contentFrame) @@ -436,13 +485,15 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { } self.updateState({ state in var state = state - switch state.data.state { - case let .confirmPassword(mode, _, _): - state.data.state = .enterPassword(mode: mode, password: "") - case let .enterHint(mode, _, _): - state.data.state = .enterPassword(mode: mode, password: "") - default: - break + if let dataState = state.data.state { + switch dataState { + case let .confirmPassword(mode, _, _): + state.data.state = .enterPassword(mode: mode, password: "") + case let .enterHint(mode, _, _): + state.data.state = .enterPassword(mode: mode, password: "") + default: + break + } } return state }, transition: .animated(duration: 0.5, curve: .spring)) @@ -457,8 +508,12 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { return } strongSelf.updateState({ state in + guard let dataState = state.data.state else { + return state + } var state = state - switch state.data.state { + + switch dataState { case let .enterPassword(mode, password): state.data.state = .confirmPassword(mode: mode, password: password, confirmation: "") case let .confirmPassword(mode, password, confirmation): @@ -592,11 +647,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { })) } case let .confirmEmail(confirmState, _, _, code): - strongSelf.updateState({ state in - var state = state - state.data.activity = true - return state - }, transition: .animated(duration: 0.5, curve: .spring)) + state.data.activity = true strongSelf.actionDisposable.set((confirmTwoStepRecoveryEmail(network: strongSelf.account.network, code: code) |> deliverOnMainQueue).start(error: { error in guard let strongSelf = self else { @@ -640,7 +691,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { return state }, transition: .animated(duration: 0.5, curve: .spring)) } - if case let .enterEmail(enterEmail) = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty { + if case let .enterEmail(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty { self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: { continueImpl() })]), nil) diff --git a/TelegramUI/UrlHandling.swift b/TelegramUI/UrlHandling.swift index 7aee778307..c836f0b85c 100644 --- a/TelegramUI/UrlHandling.swift +++ b/TelegramUI/UrlHandling.swift @@ -16,6 +16,7 @@ private enum ParsedInternalUrl { case join(String) case localization(String) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) + case internalInstantView(url: String) } private enum ParsedUrl { @@ -76,6 +77,18 @@ private func parseInternalUrl(query: String) -> ParsedInternalUrl? { if let server = server, !server.isEmpty, let port = port, let portValue = Int32(port) { return .proxy(host: server, port: portValue, username: user, password: pass, secret: secret) } + } else if peerName == "iv" { + var url: String? + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "url" { + url = value + } + } + } + if let _ = url { + return .internalInstantView(url: "https://t.me/\(query)") + } } else { for queryItem in queryItems { if let value = queryItem.value { @@ -142,6 +155,9 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig return .single(.localization(identifier)) case let .proxy(host, port, username, password, secret): return .single(.proxy(host: host, port: port, username: username, password: password, secret: secret)) + case let .internalInstantView(url): + return resolveInstantViewUrl(account: account, url: url) + |> map(Optional.init) } } @@ -203,19 +219,19 @@ func resolveUrl(account: Account, url: String) -> Signal { func resolveInstantViewUrl(account: Account, url: String) -> Signal { return webpagePreview(account: account, url: url) - |> map { webpage -> ResolvedUrl in - if let webpage = webpage, case let .Loaded(content) = webpage.content, content.instantPage != nil { - var anchorValue: String? - if let anchorRange = url.range(of: "#") { - let anchor = url[anchorRange.upperBound...] - if !anchor.isEmpty { - anchorValue = String(anchor) - } + |> map { webpage -> ResolvedUrl in + if let webpage = webpage, case let .Loaded(content) = webpage.content, content.instantPage != nil { + var anchorValue: String? + if let anchorRange = url.range(of: "#") { + let anchor = url[anchorRange.upperBound...] + if !anchor.isEmpty { + anchorValue = String(anchor) } - return .instantView(webpage, anchorValue) - } else { - return .externalUrl(url) } + return .instantView(webpage, anchorValue) + } else { + return .externalUrl(url) + } } }