mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Merge branches 'master' and 'master' of github.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
5a5241adbf
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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)?
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user