diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index c86c9d53c1..ea3f71c736 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -163,10 +163,22 @@ private final class StickerSelectionComponent: Component { }, sendEmoji: { _, _, _ in }, - sendGif: { _, _, _, _, _ in + sendGif: { [weak self] file, _, _, _, _ in + if let self, let controller = self.component?.getController() { + controller.completion(.video(file.media)) + controller.dismiss(animated: true) + } return false }, - sendBotContextResultAsGif: { _, _, _, _, _, _ in + sendBotContextResultAsGif: { [weak self] collection, result, _, _, _, _ in + if let self, let controller = self.component?.getController() { + if case let .internalReference(reference) = result { + if let file = reference.file { + controller.completion(.video(file)) + controller.dismiss(animated: true) + } + } + } return false }, updateChoosingSticker: { _ in }, @@ -320,7 +332,7 @@ private final class StickerSelectionComponent: Component { inputNodeInteraction: inputNodeInteraction, mode: mappedMode, stickerActionTitle: presentationData.strings.StickerPack_AddSticker, - trendingGifsPromise: Promise(nil), + trendingGifsPromise: self.component?.getController()?.node.trendingGifsPromise ?? Promise(nil), cancel: { }, peekBehavior: stickerPeekBehavior @@ -416,7 +428,7 @@ public class StickerPickerScreen: ViewController { private var content: StickerPickerInputData? private let contentDisposable = MetaDisposable() private var hasRecentGifsDisposable: Disposable? - private let trendingGifsPromise = Promise(nil) + fileprivate let trendingGifsPromise = Promise(nil) private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? private(set) var isExpanded = false @@ -584,6 +596,16 @@ public class StickerPickerScreen: ViewController { }) } + self.trendingGifsPromise.set(.single(nil)) + self.trendingGifsPromise.set(paneGifSearchForQuery(context: context, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil) + |> map { items -> ChatMediaInputGifPaneTrendingState? in + if let items = items { + return ChatMediaInputGifPaneTrendingState(files: items.files, nextOffset: items.nextOffset) + } else { + return nil + } + }) + self.gifInputInteraction = GifPagerContentComponent.InputInteraction( performItemAction: { [weak self] item, view, rect in guard let self else { diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index df15d41c55..9ace2cac26 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -100,7 +100,7 @@ public final class TwoFactorDataInputScreen: ViewController { super.viewWillAppear(animated) switch self.mode { - case .rememberPassword, .password: + case .rememberPassword, .password, .passwordHint, .emailAddress: (self.displayNode as? TwoFactorDataInputScreenNode)?.focus() default: break @@ -210,6 +210,7 @@ public final class TwoFactorDataInputScreen: ViewController { navigationController.replaceController(strongSelf, with: TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .passwordHint(recovery: recovery, password: values[0], doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation), animated: true) case let .emailAddress(password, hint, doneText): guard let text = (strongSelf.displayNode as! TwoFactorDataInputScreenNode).inputText.first, !text.isEmpty else { + (strongSelf.displayNode as? TwoFactorDataInputScreenNode)?.onAction(success: false) return } let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) @@ -1303,6 +1304,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS } } + private var skipIsHiddenUntilError = false + init(sharedContext: SharedAccountContext, presentationData: PresentationData, mode: TwoFactorDataInputMode, action: @escaping () -> Void, skipAction: @escaping () -> Void, changeEmailAction: @escaping () -> Void, resendCodeAction: @escaping () -> Void) { self.presentationData = presentationData self.mode = mode @@ -1413,6 +1416,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS text = NSAttributedString(string: presentationData.strings.TwoFactorSetup_Email_Text, font: Font.regular(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor) buttonText = presentationData.strings.TwoFactorSetup_Email_Action skipActionText = presentationData.strings.TwoFactorSetup_Email_SkipAction + self.skipIsHiddenUntilError = true changeEmailActionText = "" resendCodeActionText = "" inputNodes = [ @@ -1538,8 +1542,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS self.skipActionTitleNode.displaysAsynchronously = false self.skipActionTitleNode.attributedText = NSAttributedString(string: skipActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor) self.skipActionButtonNode = HighlightTrackingButtonNode() - self.skipActionTitleNode.isHidden = skipActionText.isEmpty - self.skipActionButtonNode.isHidden = skipActionText.isEmpty + self.skipActionTitleNode.isHidden = skipActionText.isEmpty || self.skipIsHiddenUntilError + self.skipActionButtonNode.isHidden = skipActionText.isEmpty || self.skipIsHiddenUntilError self.changeEmailActionTitleNode = ImmediateTextNode() self.changeEmailActionTitleNode.isUserInteractionEnabled = false @@ -1707,8 +1711,8 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS case .emailAddress, .updateEmailAddress, .passwordRecovery: let hasText = strongSelf.inputNodes.contains(where: { !$0.text.isEmpty }) strongSelf.buttonNode.isHidden = !hasText - strongSelf.skipActionTitleNode.isHidden = hasText - strongSelf.skipActionButtonNode.isHidden = hasText + strongSelf.skipActionTitleNode.isHidden = hasText || strongSelf.skipIsHiddenUntilError + strongSelf.skipActionButtonNode.isHidden = hasText || strongSelf.skipIsHiddenUntilError case let .emailConfirmation(_, _, codeLength, _): let text = strongSelf.inputNodes[0].text let hasText = !text.isEmpty @@ -1822,35 +1826,48 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, UIS func onAction(success: Bool) { switch self.mode { - case .rememberPassword: - if !success { + case .emailAddress: + if !success { + self.inputNodes.first?.layer.addShakeAnimation() + HapticFeedback().error() + + if self.skipIsHiddenUntilError { + self.skipIsHiddenUntilError = false + self.skipActionTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.skipActionTitleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) self.skipActionTitleNode.isHidden = false self.skipActionButtonNode.isHidden = false - - let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(node: self.buttonNode, alpha: 0.0) - transition.updateAlpha(node: self.skipActionTitleNode, alpha: 1.0) - - if let snapshotView = self.textNode.view.snapshotContentTree() { - snapshotView.frame = self.textNode.view.frame - self.textNode.view.superview?.addSubview(snapshotView) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - - self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.TwoFactorRemember_WrongPassword, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemDestructiveColor) - self.inputNodes.first?.isFailed = true - - if let (layout, navigationHeight) = self.validLayout { - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) - } } - default: - break + } + case .rememberPassword: + if !success { + self.skipActionTitleNode.isHidden = false + self.skipActionButtonNode.isHidden = false + + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.buttonNode, alpha: 0.0) + transition.updateAlpha(node: self.skipActionTitleNode, alpha: 1.0) + + if let snapshotView = self.textNode.view.snapshotContentTree() { + snapshotView.frame = self.textNode.view.frame + self.textNode.view.superview?.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.TwoFactorRemember_WrongPassword, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemDestructiveColor) + self.inputNodes.first?.isFailed = true + + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) + } + } + default: + break } } diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index a564929778..0cc9414914 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -401,7 +401,8 @@ public final class PeerListItemComponent: Component { let labelData: (String, Bool) if let presence = component.presence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(timestamp)) + let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat + labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: dateTimeFormat, presence: presence, relativeTo: Int32(timestamp)) } else if let subtitle = component.subtitle { labelData = (subtitle, false) } else { diff --git a/submodules/WebSearchUI/BUILD b/submodules/WebSearchUI/BUILD index 77b829ca44..e8b8681c49 100644 --- a/submodules/WebSearchUI/BUILD +++ b/submodules/WebSearchUI/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/AppBundle:AppBundle", "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/AttachmentUI:AttachmentUI", + "//submodules/RadialStatusNode:RadialStatusNode", ], visibility = [ "//visibility:public", diff --git a/submodules/WebSearchUI/Sources/WebSearchItem.swift b/submodules/WebSearchUI/Sources/WebSearchItem.swift index 98df5e9c31..cc3b7f9587 100644 --- a/submodules/WebSearchUI/Sources/WebSearchItem.swift +++ b/submodules/WebSearchUI/Sources/WebSearchItem.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import TelegramPresentationData import CheckNode import PhotoResources +import RadialStatusNode final class WebSearchItem: GridItem { var section: GridSection? @@ -44,6 +45,7 @@ final class WebSearchItemNode: GridItemNode { private let imageNodeBackground: ASDisplayNode private let imageNode: TransformImageNode private var checkNode: CheckNode? + private var statusNode: RadialStatusNode? private(set) var item: WebSearchItem? private var currentDimensions: CGSize? @@ -81,6 +83,32 @@ final class WebSearchItemNode: GridItemNode { self.imageNode.view.addGestureRecognizer(recognizer) } + func updateProgress(_ value: Float?, animated: Bool) { + if let value { + let statusNode: RadialStatusNode + if let current = self.statusNode { + statusNode = current + } else { + statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6)) + statusNode.isUserInteractionEnabled = false + self.addSubnode(statusNode) + self.statusNode = statusNode + } + let adjustedProgress = max(0.027, CGFloat(value)) + let state: RadialStatusNodeState = .progress(color: .white, lineWidth: nil, value: adjustedProgress, cancelEnabled: true, animateRotation: true) + statusNode.transitionToState(state) + } else if let statusNode = self.statusNode { + self.statusNode = nil + if animated { + statusNode.transitionToState(.none, animated: true, completion: { [weak statusNode] in + statusNode?.removeFromSupernode() + }) + } else { + statusNode.removeFromSupernode() + } + } + } + func setup(item: WebSearchItem, synchronousLoad: Bool) { if self.item !== item { var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?