diff --git a/Telegram/Telegram-iOS/Resources/ShareDone.tgs b/Telegram/Telegram-iOS/Resources/ShareDone.tgs new file mode 100644 index 0000000000..1a69eec93b Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/ShareDone.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/ShareProgress.tgs b/Telegram/Telegram-iOS/Resources/ShareProgress.tgs new file mode 100644 index 0000000000..78fd296aac Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/ShareProgress.tgs differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6ab4e9500e..1271477e23 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7520,3 +7520,6 @@ Sorry for the inconvenience."; "Group.RequestToJoinSentDescriptionGroup" = "You will be able to send messages once the admins approve your request."; "Channel.AdminLog.JoinedViaPublicRequest" = "%1$@ joined via public request, approved by %2$@"; + +"Share.UploadProgress" = "Uploading • %d%"; +"Share.UploadDone" = "Done"; diff --git a/submodules/AttachmentUI/Sources/MoreButtonNode.swift b/submodules/AttachmentUI/Sources/MoreButtonNode.swift index 3317af62bf..1c14ba94cc 100644 --- a/submodules/AttachmentUI/Sources/MoreButtonNode.swift +++ b/submodules/AttachmentUI/Sources/MoreButtonNode.swift @@ -111,13 +111,13 @@ public final class MoreButtonNode: ASDisplayNode { } } - @objc private func buttonPressed() { + @objc public func buttonPressed() { self.action?(self.contextSourceNode, nil) if case .more = self.iconNode.iconState { self.iconNode.play() } } - + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let animationSize = CGSize(width: 30.0, height: 30.0) let inset: CGFloat = 0.0 diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index e800808d51..e212ae23f5 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -542,6 +542,24 @@ public final class ChatMessageBubbleBackdrop: ASDisplayNode { self.backgroundContent?.offsetSpring(value: value, duration: duration, damping: damping) } + public func updateFrame(_ value: CGRect, animator: ControlledTransitionAnimator, completion: @escaping () -> Void = {}) { + if let maskView = self.maskView { + animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset), completion: nil) + } + if let backgroundContent = self.backgroundContent { + animator.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)), completion: nil) + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .animated(duration: animator.duration, curve: .spring)) + } + } + animator.updateFrame(layer: self.layer, frame: value, completion: { _ in + completion() + }) + } + public func updateFrame(_ value: CGRect, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { if let maskView = self.maskView { transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset)) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 61ed98cb27..09a9a7f715 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -113,6 +113,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let chatLocation: ChatLocation? private let bannedSendMedia: (Int32, Bool)? private let collection: PHAssetCollection? + private let saveEditedPhotos: Bool private let titleView: MediaPickerTitleView private let moreButtonNode: MoreButtonNode @@ -687,7 +688,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } let proceed: (Bool) -> Void = { convertToJpeg in - guard let signals = TGMediaAssetsController.resultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else { + guard let signals = TGMediaAssetsController.resultSignals(for: controller.interaction?.selectionState, editingContext: controller.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: controller.saveEditedPhotos) else { return } controller.completed = true @@ -1066,7 +1067,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private var isDismissing = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?, collection: PHAssetCollection? = nil, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?, collection: PHAssetCollection? = nil, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, saveEditedPhotos: Bool = false) { self.context = context let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } @@ -1076,6 +1077,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.chatLocation = chatLocation self.bannedSendMedia = bannedSendMedia self.collection = collection + self.saveEditedPhotos = saveEditedPhotos self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) self.titleView.title = collection?.localizedTitle ?? presentationData.strings.Attachment_Gallery @@ -1339,7 +1341,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } @objc private func rightButtonPressed() { - self.moreButtonNode.action?(self.moreButtonNode.contextSourceNode, nil) + self.moreButtonNode.buttonPressed() } public func resetForReuse() { diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 4468f5dcb4..7e5dc1063d 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -583,7 +583,9 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { let scrubbingTimestampValue = strongSelf.scrubbingTimestampValue strongSelf.scrubbingTimestampValue = nil strongSelf._scrubbingTimestamp.set(.single(nil)) - strongSelf._scrubbingPosition.set(.single(nil)) + Queue.mainQueue().after(0.01, { + strongSelf._scrubbingPosition.set(.single(nil)) + }) if let scrubbingTimestampValue = scrubbingTimestampValue, apply { if let statusValue = strongSelf.statusValue { switch statusValue.status { diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift index 91e304a342..2b3cb4cdba 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift @@ -1085,7 +1085,7 @@ private class StorageUsageClearProgressOverlayNode: ASDisplayNode, ActionSheetGr let descriptionTextSize = self.descriptionTextNode.updateLayout(CGSize(width: size.width - inset * 3.0, height: size.height)) var descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - descriptionTextSize.width) / 2.0), y: progressFrame.minY - spacing - 9.0 - descriptionTextSize.height), size: descriptionTextSize) - self.progressTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_Progress(Int(progress * 100.0)).string, font: Font.with(size: 17.0, design: .regular, weight: .bold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + self.progressTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ClearCache_Progress(Int(progress * 100.0)).string, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.actionSheet.primaryTextColor) let progressTextSize = self.progressTextNode.updateLayout(size) var progressTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressTextSize.width) / 2.0), y: descriptionTextFrame.minY - spacing - progressTextSize.height), size: progressTextSize) diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index 80cdde020a..f6222b6c6a 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -32,6 +32,8 @@ swift_library( "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/ContextUI:ContextUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 8a004d6221..a6e1ba9bad 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -457,7 +457,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate if contentNode is ShareSearchContainerNode { self.setActionNodesHidden(true, inputField: true, actions: true) - } else if !(contentNode is ShareLoadingContainerNode) { + } else if !(contentNode is ShareLoadingContainer) { self.setActionNodesHidden(false, inputField: !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil, actions: true) } } else { @@ -649,7 +649,12 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate let timestamp = CACurrentMediaTime() let doneImpl: (Bool) -> Void = { [weak self] shouldDelay in let minDelay: Double = shouldDelay ? 0.9 : 0.6 - let delay = max(minDelay, (timestamp + minDelay) - CACurrentMediaTime()) + let delay: Double + if let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareProlongedLoadingContainerNode { + delay = contentNode.completionDuration + } else { + delay = max(minDelay, (timestamp + minDelay) - CACurrentMediaTime()) + } Queue.mainQueue().after(delay, { self?.animateOut(shared: true, completion: { self?.dismiss?(true) @@ -658,7 +663,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate }) } if self.fromForeignApp { - self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: true), fastOut: true) + self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true), fastOut: true) } else { self.animateOut(shared: true, completion: { }) @@ -683,7 +688,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate return } - guard let contentNode = strongSelf.contentNode as? ShareLoadingContainerNode else { + guard let contentNode = strongSelf.contentNode as? ShareLoadingContainer else { return } @@ -988,7 +993,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0) transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0) - self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: true), fastOut: true) + self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true), fastOut: true) let timestamp = CACurrentMediaTime() self.shareDisposable.set(signal.start(completed: { [weak self] in let minDelay = 0.6 @@ -1041,14 +1046,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate } self.shareDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] status in - guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainerNode else { + guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainer else { return } if let status = status { contentNode.state = .progress(status) } }, completed: { [weak self] in - guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainerNode else { + guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainer else { return } contentNode.state = .done diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index 6fc827b501..7cd36680a8 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -1,11 +1,14 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit import Display import Postbox import TelegramPresentationData import ActivityIndicator import RadialStatusNode +import AnimatedStickerNode +import TelegramAnimatedStickerNode public enum ShareLoadingState { case preparing @@ -13,7 +16,11 @@ public enum ShareLoadingState { case done } -public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContainerNode { +protocol ShareLoadingContainer: ASDisplayNode { + var state: ShareLoadingState { get set } +} + +public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContainerNode, ShareLoadingContainer { private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? private let theme: PresentationTheme @@ -83,3 +90,229 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain public func updateSelectedPeers() { } } + +public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareContentContainerNode, ShareLoadingContainer { + private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? + + private let theme: PresentationTheme + private let strings: PresentationStrings + + private let animationNode: AnimatedStickerNode + private let doneAnimationNode: AnimatedStickerNode + private let progressTextNode: ImmediateTextNode + + private let progressBackgroundNode: ASDisplayNode + private let progressForegroundNode: ASDisplayNode + + private let animationStatusDisposable = MetaDisposable() + + private var progressValue: CGFloat = 0.0 + + private var randomCompletionStart: CGFloat = .random(in: 0.94...0.97) + private var completionProgress: Double = 0.0 + private var isDone: Bool = false + + private var startTimestamp: Double? + + public var state: ShareLoadingState = .preparing { + didSet { + switch self.state { + case .preparing: + break + case let .progress(value): + let currentTimestamp = CACurrentMediaTime() + if self.startTimestamp == nil { + self.startTimestamp = currentTimestamp + } else if let startTimestamp = self.startTimestamp, currentTimestamp - startTimestamp < 1.0, value > 0.5 && value < 0.9 { + self.randomCompletionStart = 0.8 + } + + if let (size, isLandscape, bottomInset) = self.validLayout { + self.updateLayout(size: size, isLandscape: isLandscape, bottomInset: bottomInset, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + case .done: + if let (size, isLandscape, bottomInset) = self.validLayout { + self.updateLayout(size: size, isLandscape: isLandscape, bottomInset: bottomInset, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + self.animationNode.stopAtNearestLoop = true + self.animationNode.completed = { [weak self] _ in + if let strongSelf = self { + strongSelf.animationNode.visibility = false + strongSelf.doneAnimationNode.visibility = true + strongSelf.doneAnimationNode.isHidden = false + } + } + self.animationNode.frameUpdated = { [weak self] index, total in + if let strongSelf = self { + let progress = min(1.0, CGFloat(index) / CGFloat(total)) + if abs(progress - strongSelf.completionProgress) >= 0.05 || progress == 1.0 { + strongSelf.completionProgress = 0.5 * progress + + if let (size, isLandscape, bottomInset) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, isLandscape: isLandscape, bottomInset: bottomInset, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + } + } + self.doneAnimationNode.frameUpdated = { [weak self] index, total in + if let strongSelf = self { + let progress = 0.5 + min(1.0, CGFloat(index) / CGFloat(total) * 2.1) * 0.5 + if abs(progress - strongSelf.completionProgress) >= 0.05 || progress == 1.0 { + strongSelf.completionProgress = progress + + if progress == 1.0, !strongSelf.isDone { + strongSelf.isDone = true + + if let snapshotView = strongSelf.progressTextNode.view.snapshotContentTree() { + snapshotView.frame = strongSelf.progressTextNode.frame + strongSelf.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.25, removeOnCompletion: false, additive: true) + + strongSelf.progressTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + strongSelf.progressTextNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.25, additive: true) + } + } + + if let (size, isLandscape, bottomInset) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, isLandscape: isLandscape, bottomInset: bottomInset, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + } + } + self.doneAnimationNode.started = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.animationNode.isHidden = true + } + } + } + } + + private var elapsedTime: Double = 0.0 + public var completionDuration: Double { + return self.elapsedTime + 3.0 + 0.15 + } + + public init(theme: PresentationTheme, strings: PresentationStrings, forceNativeAppearance: Bool) { + self.theme = theme + self.strings = strings + + self.animationNode = AnimatedStickerNode() + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ShareProgress"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode.visibility = true + + self.doneAnimationNode = AnimatedStickerNode() + self.doneAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ShareDone"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + self.doneAnimationNode.visibility = false + self.doneAnimationNode.isHidden = true + + self.progressTextNode = ImmediateTextNode() + self.progressTextNode.textAlignment = .center + + self.progressBackgroundNode = ASDisplayNode() + self.progressBackgroundNode.backgroundColor = theme.actionSheet.controlAccentColor.withMultipliedAlpha(0.2) + self.progressBackgroundNode.cornerRadius = 3.0 + + self.progressForegroundNode = ASDisplayNode() + self.progressForegroundNode.backgroundColor = theme.actionSheet.controlAccentColor + self.progressForegroundNode.cornerRadius = 3.0 + + super.init() + + self.addSubnode(self.animationNode) + self.addSubnode(self.doneAnimationNode) + + self.addSubnode(self.progressTextNode) + + self.addSubnode(self.progressBackgroundNode) + self.addSubnode(self.progressForegroundNode) + + self.animationStatusDisposable.set((self.animationNode.status + |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self { + strongSelf.elapsedTime = status.duration - status.timestamp + } + })) + } + + deinit { + self.animationStatusDisposable.dispose() + } + + public func activate() { + } + + public func deactivate() { + } + + public func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + } + + public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) { + self.contentOffsetUpdated = f + } + + private var validLayout: (CGSize, Bool, CGFloat)? + public func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, isLandscape, bottomInset) + + let nodeHeight: CGFloat = 400.0 + + let inset: CGFloat = 24.0 + let progressHeight: CGFloat = 6.0 + let spacing: CGFloat = 16.0 + + var progress: CGFloat + switch self.state { + case .preparing: + progress = 0.0 + case let .progress(value): + progress = CGFloat(value) * self.randomCompletionStart + case .done: + progress = self.randomCompletionStart + (1.0 - self.randomCompletionStart) * self.completionProgress + } + self.progressValue = max(self.progressValue, progress) + progress = self.progressValue + + let progressFrame = CGRect(x: inset, y: size.height - inset - progressHeight, width: size.width - inset * 2.0, height: progressHeight) + self.progressBackgroundNode.frame = progressFrame + let progressForegroundFrame = CGRect(x: progressFrame.minX, y: progressFrame.minY, width: floorToScreenPixels(progressFrame.width * progress), height: progressHeight) + if !self.progressForegroundNode.frame.width.isZero { + transition.updateFrame(node: self.progressForegroundNode, frame: progressForegroundFrame) + } else { + self.progressForegroundNode.frame = progressForegroundFrame + } + + let progressText: String + if self.isDone { + progressText = self.strings.Share_UploadDone + } else { + progressText = self.strings.Share_UploadProgress(Int(progress * 100.0)).string + } + + self.progressTextNode.attributedText = NSAttributedString(string: progressText, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.theme.actionSheet.primaryTextColor) + let progressTextSize = self.progressTextNode.updateLayout(size) + let progressTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressTextSize.width) / 2.0), y: progressFrame.minY - spacing - 9.0 - progressTextSize.height), size: progressTextSize) + self.progressTextNode.frame = progressTextFrame + + let imageSide: CGFloat = 160.0 + let imageSize = CGSize(width: imageSide, height: imageSide) + + let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (progressTextFrame.minY - imageSize.height - 20.0)), size: imageSize) + self.animationNode.frame = animationFrame + self.animationNode.updateLayout(size: imageSize) + + self.doneAnimationNode.frame = animationFrame + self.doneAnimationNode.updateLayout(size: imageSize) + + self.contentOffsetUpdated?(-size.height + nodeHeight * 0.5, transition) + } + + public func updateSelectedPeers() { + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ac4c31bb2e..64a7a36c93 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10703,13 +10703,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { buttons = .single((availableButtons, .gallery)) } + + let dataSettings = self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } - let _ = (buttons - |> deliverOnMainQueue).start(next: { [weak self] buttons, initialButton in + let _ = combineLatest(queue: Queue.mainQueue(), buttons, dataSettings).start(next: { [weak self] buttonsAndInitialButton, dataSettings in guard let strongSelf = self else { return } + let (buttons, initialButton) = buttonsAndInitialButton + guard let initialButton = initialButton else { if let botId = botId { let _ = (context.engine.messages.getAttachMenuBot(botId: botId) @@ -10751,7 +10757,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.prepareForReuse() return } - strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in + strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in let _ = currentMediaController.swap(controller) if !inputText.string.isEmpty { mediaPickerContext?.setCaption(inputText) @@ -11435,11 +11441,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + private func presentMediaPicker(saveEditedPhotos: Bool, bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } - let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia) + let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia, saveEditedPhotos: saveEditedPhotos) let mediaPickerContext = controller.mediaPickerContext controller.openCamera = { [weak self] cameraView in self?.openCamera(cameraView: cameraView) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 2495e2d16e..9f7f6ac185 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -2764,31 +2764,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode shareButtonNode.removeFromSupernode() } - if case .System = animation/*, !strongSelf.mainContextSourceNode.isExtractedToContextPreview*/ { + if case let .System(duration, _) = animation/*, !strongSelf.mainContextSourceNode.isExtractedToContextPreview*/ { if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) { - animation.animator.updateFrame(layer: strongSelf.backgroundNode.layer, frame: backgroundFrame, completion: nil) - animation.animator.updatePosition(layer: strongSelf.clippingNode.layer, position: backgroundFrame.center, completion: nil) - strongSelf.clippingNode.clipsToBounds = true - animation.animator.updateBounds(layer: strongSelf.clippingNode.layer, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak strongSelf] _ in - let _ = strongSelf - //strongSelf?.clippingNode.clipsToBounds = false - }) + if useDisplayLinkAnimations { + let backgroundAnimation = ListViewAnimation(from: strongSelf.backgroundNode.frame, to: backgroundFrame, duration: duration * UIView.animationDurationFactor(), curve: strongSelf.preferredAnimationCurve, beginAt: beginAt, update: { [weak strongSelf] _, frame in + if let strongSelf = strongSelf { + strongSelf.backgroundNode.frame = frame + strongSelf.clippingNode.position = CGPoint(x: frame.midX, y: frame.midY) + strongSelf.clippingNode.bounds = CGRect(origin: CGPoint(x: frame.minX, y: frame.minY), size: frame.size) + + strongSelf.backgroundNode.updateLayout(size: frame.size, transition: .immediate) + strongSelf.backgroundWallpaperNode.updateFrame(frame, transition: .immediate) + strongSelf.shadowNode.updateLayout(backgroundFrame: frame, transition: .immediate) + } + }) + strongSelf.setAnimationForKey("backgroundNodeFrame", animation: backgroundAnimation) + } else { + animation.animator.updateFrame(layer: strongSelf.backgroundNode.layer, frame: backgroundFrame, completion: nil) + animation.animator.updatePosition(layer: strongSelf.clippingNode.layer, position: backgroundFrame.center, completion: nil) + strongSelf.clippingNode.clipsToBounds = true + animation.animator.updateBounds(layer: strongSelf.clippingNode.layer, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak strongSelf] _ in + let _ = strongSelf + //strongSelf?.clippingNode.clipsToBounds = false + }) - strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: animation) - animation.animator.updateFrame(layer: strongSelf.backgroundWallpaperNode.layer, frame: backgroundFrame, completion: nil) - strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: animation.transition) - strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: animation.transition) + strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: animation) + animation.animator.updateFrame(layer: strongSelf.backgroundWallpaperNode.layer, frame: backgroundFrame, completion: nil) + strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: animation.transition) + strongSelf.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: animation.transition) + } if let _ = strongSelf.backgroundNode.type { - /*var incomingOffset: CGFloat = 0.0 - switch type { - case .incoming: - incomingOffset = 5.0 - default: - break - }*/ - //strongSelf.mainContextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0) - //strongSelf.mainContainerNode.targetNodeForActivationProgressContentRect = strongSelf.mainContextSourceNode.contentRect if !strongSelf.mainContextSourceNode.isExtractedToContextPreview { if let (rect, size) = strongSelf.absoluteRect { strongSelf.updateAbsoluteRect(rect, within: size) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 7236e683f6..6081c9d4a5 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -1984,6 +1984,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let subtitleNodeRawContainer: ASDisplayNode let subtitleNode: MultiScaleTextNode let panelSubtitleNode: MultiScaleTextNode + let nextPanelSubtitleNode: MultiScaleTextNode let usernameNodeContainer: ASDisplayNode let usernameNodeRawContainer: ASDisplayNode let usernameNode: MultiScaleTextNode @@ -2045,6 +2046,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.panelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) self.panelSubtitleNode.displaysAsynchronously = false + self.nextPanelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) + self.nextPanelSubtitleNode.displaysAsynchronously = false + self.usernameNodeContainer = ASDisplayNode() self.usernameNodeRawContainer = ASDisplayNode() self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) @@ -2101,6 +2105,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.titleNodeContainer.addSubnode(self.titleNode) self.subtitleNodeContainer.addSubnode(self.subtitleNode) self.subtitleNodeContainer.addSubnode(self.panelSubtitleNode) +// self.subtitleNodeContainer.addSubnode(self.nextPanelSubtitleNode) self.usernameNodeContainer.addSubnode(self.usernameNode) self.regularContentNode.addSubnode(self.avatarListNode) @@ -2367,6 +2372,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let smallSubtitleString: NSAttributedString let subtitleString: NSAttributedString var panelSubtitleString: NSAttributedString? + var nextPanelSubtitleString: NSAttributedString? let usernameString: NSAttributedString if let peer = peer { isVerified = peer.isVerified @@ -2415,7 +2421,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { subtitleString = NSAttributedString(string: statusData.text, font: Font.regular(17.0), textColor: subtitleColor) usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) - let (maybePanelStatusData, _, _) = panelStatusData + let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData if let panelStatusData = maybePanelStatusData { let subtitleColor: UIColor if panelStatusData.isActivity { @@ -2425,6 +2431,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { } panelSubtitleString = NSAttributedString(string: panelStatusData.text, font: Font.regular(17.0), textColor: subtitleColor) } + if let nextPanelStatusData = maybeNextPanelStatusData { + nextPanelSubtitleString = NSAttributedString(string: nextPanelStatusData.text, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + } } else { subtitleString = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) smallSubtitleString = subtitleString @@ -2477,6 +2486,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { ], mainState: TitleNodeStateRegular) self.panelSubtitleNode.accessibilityLabel = (panelSubtitleString ?? subtitleString).string + let nextPanelSubtitleNodeLayout = self.nextPanelSubtitleNode.updateLayout(states: [ + TitleNodeStateRegular: MultiScaleTextState(attributedText: nextPanelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize), + TitleNodeStateExpanded: MultiScaleTextState(attributedText: nextPanelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize) + ], mainState: TitleNodeStateRegular) + if let _ = nextPanelSubtitleString { + self.nextPanelSubtitleNode.isHidden = false + } + let usernameNodeLayout = self.usernameNode.updateLayout(states: [ TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)), TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height)) @@ -2490,6 +2507,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size + let _ = nextPanelSubtitleNodeLayout[TitleNodeStateRegular]!.size let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size if let image = self.titleCredibilityIconNode.image { @@ -2596,6 +2614,11 @@ final class PeerInfoHeaderNode: ASDisplayNode { TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0 ], alpha: panelSubtitleAlpha, transition: transition) + self.nextPanelSubtitleNode.update(stateFractions: [ + TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0, + TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0 + ], alpha: panelSubtitleAlpha, transition: transition) + self.usernameNode.update(stateFractions: [ TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0, TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0 @@ -2747,6 +2770,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize())) transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize())) transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize())) + transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize())) transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize())) transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale) transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale) @@ -2789,6 +2813,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize())) transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize())) + transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize())) transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize())) transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale) transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale) diff --git a/submodules/TranslateUI/Sources/Translate.swift b/submodules/TranslateUI/Sources/Translate.swift index 7d385dbb01..99cea256b3 100644 --- a/submodules/TranslateUI/Sources/Translate.swift +++ b/submodules/TranslateUI/Sources/Translate.swift @@ -146,7 +146,7 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat return (false, nil) } - if #available(iOS 15.0, *) { + if #available(iOS 12.0, *) { var dontTranslateLanguages: [String] = [] if let ignoredLanguages = ignoredLanguages { dontTranslateLanguages = ignoredLanguages diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 34df4b4d8a..ce125e577e 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -624,7 +624,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } @objc private func moreButtonPressed() { - self.moreButtonNode.action?(self.moreButtonNode.contextSourceNode, nil) + self.moreButtonNode.buttonPressed() } @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {