Merge branches 'master' and 'master' of github.com:peter-iakovlev/TelegramUI

This commit is contained in:
Ilya Laktyushin 2018-11-13 02:05:28 +04:00
commit 5a5241adbf
24 changed files with 512 additions and 293 deletions

View File

@ -476,9 +476,33 @@ final class BotCheckoutControllerNode: ItemListControllerNode<BotCheckoutEntry>,
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

View File

@ -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 {

View File

@ -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<Never, NoError> { 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)

View File

@ -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))

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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)?

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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<MediaPlayerStatus>()
var status: Signal<MediaPlayerStatus, NoError> {
@ -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()
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -888,42 +888,55 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode<SecureIdPlai
innerState.actionState = .saving
self.updateInnerState(transition: .immediate, with: innerState)
self.actionDisposable.set((secureIdPrepareEmailVerification(network: self.account.network, value: SecureIdEmailValue(email: value))
|> 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)
}))
}))
}
}

View File

@ -135,7 +135,7 @@ final class SecureIdValueFormFileItemNode: FormBlockItemNode<SecureIdValueFormFi
}
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: imageFrame.minX + floor((imageFrame.width - progressSize) / 2.0), y: imageFrame.minY + floor((imageFrame.height - progressSize) / 2.0)), size: CGSize(width: progressSize, height: progressSize)))
let makeLayout = self.imageNode.asyncLayout()
makeLayout(TransformImageArguments(corners: ImageCorners(radius: 6.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
makeLayout(TransformImageArguments(corners: ImageCorners(radius: 6.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: theme.list.mediaPlaceholderColor))()
if resourceUpdated {
if let resource = item.document?.resource {
self.imageNode.setSignal(securePhoto(account: item.account, resource: resource, accessContext: item.context))

View File

@ -71,8 +71,10 @@ final class SetupTwoStepVerificationContentNode: ASDisplayNode, UITextFieldDeleg
self.inputNode.textField.textContentType = .oneTimeCode
}
case .email:
self.inputNode.textField.autocapitalizationType = .none
self.inputNode.textField.autocorrectionType = .no
self.inputNode.textField.keyboardType = .emailAddress
if #available(iOSApplicationExtension 10.0, *) {
self.inputNode.textField.textContentType = .emailAddress
}

View File

@ -6,6 +6,7 @@ import TelegramCore
import SwiftSignalKit
enum SetupTwoStepVerificationInitialState {
case automatic
case createPassword
case updatePassword(current: String, hasRecoveryEmail: Bool, hasSecureValues: Bool)
case addEmail(hadRecoveryEmail: Bool, hasSecureValues: Bool, password: String)
@ -75,8 +76,10 @@ private enum SetupTwoStepVerificationState: Equatable {
}
extension SetupTwoStepVerificationState {
init(initialState: SetupTwoStepVerificationInitialState) {
init?(initialState: SetupTwoStepVerificationInitialState) {
switch initialState {
case .automatic:
return nil
case .createPassword:
self = .enterPassword(mode: .create, password: "")
case let .updatePassword(current, hasRecoveryEmail, hasSecureValues):
@ -91,7 +94,7 @@ extension SetupTwoStepVerificationState {
private struct SetupTwoStepVerificationControllerDataState: Equatable {
var activity: Bool
var state: SetupTwoStepVerificationState
var state: SetupTwoStepVerificationState?
}
private struct SetupTwoStepVerificationControllerLayoutState: Equatable {
@ -140,6 +143,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode {
private let dismiss: () -> 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)

View File

@ -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<ResolvedUrl, NoError> {
func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
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)
}
}
}