From 7849157a16759e2ba56da6978e08d2bf7aea6aef Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 15 Dec 2021 00:08:44 +0400 Subject: [PATCH] Various Improvements --- .../ChatItemGalleryFooterContentNode.swift | 19 +++ .../ChatVideoGalleryItemScrubberView.swift | 9 +- .../GalleryUI/Sources/GalleryController.swift | 12 +- .../Items/UniversalVideoGalleryItem.swift | 114 ++++++++++++------ .../LegacyComponents/TGPhotoToolbarView.h | 4 +- .../TGMediaPickerGalleryInterfaceView.m | 4 +- .../TGMediaPickerSendActionSheetController.m | 4 + .../Sources/TGPhotoEditorController.m | 4 +- .../Sources/TGPhotoToolbarView.m | 18 +-- .../Sources/MediaPlayerScrubbingNode.swift | 85 ++++++++----- .../QrCodeUI/Sources/QrCodeScreen.swift | 17 ++- .../Snowflake.imageset/Contents.json | 21 ++++ .../Snowflake.imageset/Snowflake.png | Bin 0 -> 5794 bytes .../Sources/GenerateTextEntities.swift | 2 +- .../Sources/WallpaperBackgroundNode.swift | 59 ++++++++- 15 files changed, 280 insertions(+), 92 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index ba741ecf4b..eee044e16d 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -280,6 +280,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated) } + private var hasExpandedCaptionPromise = ValuePromise(false) + var hasExpandedCaption: Signal { + return hasExpandedCaptionPromise.get() + } + init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) { self.context = context self.presentationData = presentationData @@ -716,6 +721,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.requestLayout?(.immediate) } + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.hasExpandedCaptionPromise.set(true) + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.hasExpandedCaptionPromise.set(scrollView.contentOffset.y > 1.0) + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.hasExpandedCaptionPromise.set(scrollView.contentOffset.y > 1.0) + } + override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset) diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index 6c0bebacfd..be6ff20f02 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -58,8 +58,9 @@ final class ChatVideoGalleryItemScrubberView: UIView { var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in } var seek: (Double) -> Void = { _ in } - override init(frame: CGRect) { - self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: self.chapters)) + init(chapters: [MediaPlayerScrubbingChapter]) { + self.chapters = chapters + self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: chapters)) self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white) self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white) @@ -71,7 +72,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.infoNode.isUserInteractionEnabled = false self.infoNode.displaysAsynchronously = false - super.init(frame: frame) + super.init(frame: CGRect()) self.scrubberNode.seek = { [weak self] timestamp in self?.seek(timestamp) @@ -167,7 +168,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.leftTimestampNode.status = mappedStatus self.rightTimestampNode.status = mappedStatus - if let mappedStatus = mappedStatus, false { + if let mappedStatus = mappedStatus { self.chapterDisposable.set((mappedStatus |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self, status.duration > 1.0 { diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 296165a905..37eafd2d22 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -175,7 +175,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: file.duration.flatMap(Double.init)) { entities = result } - + let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities) return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, displayInfoOnTop: displayInfoOnTop, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present) } else { @@ -218,7 +218,15 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese } } if let content = content { - return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present) + var description: NSAttributedString? + if let descriptionText = webpageContent.text { + var entities: [MessageTextEntity] = [] + if let result = addLocallyGeneratedEntities(descriptionText, enabledTypes: [.timecode], entities: entities, mediaDuration: 86400) { + entities = result + } + description = galleryCaptionStringWithAppliedEntities(descriptionText, entities: entities) + } + return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), description: description, displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present) } else { return nil } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 243b453c03..e94d62c4a0 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -21,6 +21,7 @@ import UndoUI import TelegramUIPreferences import OpenInExternalAppUI import AVKit +import TextFormat public enum UniversalVideoGalleryItemContentInfo { case message(Message) @@ -39,6 +40,7 @@ public class UniversalVideoGalleryItem: GalleryItem { let indexData: GalleryItemIndexData? let contentInfo: UniversalVideoGalleryItemContentInfo? let caption: NSAttributedString + let description: NSAttributedString? let credit: NSAttributedString? let displayInfoOnTop: Bool let hideControls: Bool @@ -54,7 +56,7 @@ public class UniversalVideoGalleryItem: GalleryItem { let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void let present: (ViewController, Any?) -> Void - public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: @escaping () -> Double?, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, present: @escaping (ViewController, Any?) -> Void) { + public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, description: NSAttributedString? = nil, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: @escaping () -> Double?, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context self.presentationData = presentationData self.content = content @@ -62,6 +64,7 @@ public class UniversalVideoGalleryItem: GalleryItem { self.indexData = indexData self.contentInfo = contentInfo self.caption = caption + self.description = description self.credit = credit self.displayInfoOnTop = displayInfoOnTop self.hideControls = hideControls @@ -707,7 +710,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>() fileprivate var titleContentView: GalleryTitleView? - private let scrubberView: ChatVideoGalleryItemScrubberView + private var scrubberView: ChatVideoGalleryItemScrubberView? private let footerContentNode: ChatItemGalleryFooterContentNode private let overlayContentNode: UniversalVideoGalleryItemOverlayNode @@ -762,7 +765,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let isInteractingPromise = ValuePromise(false, ignoreRepeated: true) private let controlsVisiblePromise = ValuePromise(true, ignoreRepeated: true) private let isShowingContextMenuPromise = ValuePromise(false, ignoreRepeated: true) - private let hasExpandedCaptionPromise = ValuePromise(false, ignoreRepeated: true) + private let hasExpandedCaptionPromise = Promise() private var hideControlsDisposable: Disposable? var playbackCompleted: (() -> Void)? @@ -774,10 +777,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context self.presentationData = presentationData - self.scrubberView = ChatVideoGalleryItemScrubberView() + self.footerContentNode = ChatItemGalleryFooterContentNode(context: context, presentationData: presentationData, present: present) - self.footerContentNode.scrubberView = self.scrubberView + self.hasExpandedCaptionPromise.set(self.footerContentNode.hasExpandedCaption) + self.footerContentNode.performAction = performAction self.footerContentNode.openActionOptions = openActionOptions @@ -806,34 +810,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self?.updateOrientation(toLandscape ? .landscapeRight : .portrait) } - self.scrubberView.seek = { [weak self] timecode in - self?.videoNode?.seek(timecode) - } - - self.scrubberView.updateScrubbing = { [weak self] timecode in - guard let strongSelf = self else { - return - } - - strongSelf.isInteractingPromise.set(timecode != nil) - - if let videoFramePreview = strongSelf.videoFramePreview { - if let timecode = timecode { - if !strongSelf.scrubbingFrames { - strongSelf.scrubbingFrames = true - strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames - |> map(Optional.init)) - } - videoFramePreview.generateFrame(at: timecode) - } else { - strongSelf.isInteractingPromise.set(false) - strongSelf.scrubbingFrame.set(.single(nil)) - videoFramePreview.cancelPendingFrames() - strongSelf.scrubbingFrames = false - } - } - } - self.statusButtonNode.addSubnode(self.statusNode) self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside) @@ -1013,6 +989,68 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { func setupItem(_ item: UniversalVideoGalleryItem) { if self.item?.content.id != item.content.id { + func parseChapters(_ string: NSAttributedString) -> [MediaPlayerScrubbingChapter] { + var timecodeRanges: [(NSRange, TelegramTimecode)] = [] + var lineRanges: [NSRange] = [] + string.enumerateAttributes(in: NSMakeRange(0, string.length), options: [], using: { attributes, range, _ in + if let timecode = attributes[NSAttributedString.Key(TelegramTextAttributes.Timecode)] as? TelegramTimecode { + timecodeRanges.append((range, timecode)) + } + }) + (string.string as NSString).enumerateSubstrings(in: NSMakeRange(0, string.length), options: .byLines, using: { _, range, _, _ in + lineRanges.append(range) + }) + + var chapters: [MediaPlayerScrubbingChapter] = [] + for (timecodeRange, timecode) in timecodeRanges { + inner: for lineRange in lineRanges { + if lineRange.contains(timecodeRange.location) { + if lineRange.length > timecodeRange.length { + var title = ((string.string as NSString).substring(with: lineRange) as NSString).replacingCharacters(in: NSMakeRange(timecodeRange.location - lineRange.location, timecodeRange.length), with: "") + title = title.trimmingCharacters(in: .whitespacesAndNewlines).trimmingCharacters(in: .punctuationCharacters) + chapters.append(MediaPlayerScrubbingChapter(title: title, start: timecode.time)) + } + break inner + } + } + } + return chapters + } + + var chapters = parseChapters(item.caption) + if chapters.isEmpty, let description = item.description { + chapters = parseChapters(description) + } + let scrubberView = ChatVideoGalleryItemScrubberView(chapters: chapters) + self.scrubberView = scrubberView + scrubberView.seek = { [weak self] timecode in + self?.videoNode?.seek(timecode) + } + scrubberView.updateScrubbing = { [weak self] timecode in + guard let strongSelf = self else { + return + } + + strongSelf.isInteractingPromise.set(timecode != nil) + + if let videoFramePreview = strongSelf.videoFramePreview { + if let timecode = timecode { + if !strongSelf.scrubbingFrames { + strongSelf.scrubbingFrames = true + strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames + |> map(Optional.init)) + } + videoFramePreview.generateFrame(at: timecode) + } else { + strongSelf.isInteractingPromise.set(false) + strongSelf.scrubbingFrame.set(.single(nil)) + videoFramePreview.cancelPendingFrames() + strongSelf.scrubbingFrames = false + } + } + } + self.footerContentNode.scrubberView = scrubberView + self.isPlayingPromise.set(false) if item.hideControls { @@ -1114,7 +1152,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.updateDisplayPlaceholder(!videoNode.ownsContentNode) } - self.scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in + scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in if let value = value, !value.duration.isZero { return value } else { @@ -1122,7 +1160,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } }) - self.scrubberView.setBufferingStatusSignal(videoNode.bufferingStatus) + scrubberView.setBufferingStatusSignal(videoNode.bufferingStatus) self.requiresDownload = true var mediaFileStatus: Signal = .single(nil) @@ -1174,7 +1212,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file) if !isWebpage { - self.scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size) + scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size) } self.requiresDownload = !isMediaStreamable(message: message, media: file) @@ -1578,7 +1616,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch action { case let .timecode(timecode): - self.scrubberView.animateTo(timecode) + self.scrubberView?.animateTo(timecode) videoNode.seek(timecode) } } @@ -2586,7 +2624,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { override func adjustForPreviewing() { super.adjustForPreviewing() - self.scrubberView.isHidden = true + self.scrubberView?.isHidden = true } override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index 961d7d9621..ee8e04a8db 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -1,5 +1,7 @@ #import +#import + typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { TGPhotoEditorNoneTab = 0, TGPhotoEditorCropTab = 1 << 0, @@ -50,7 +52,7 @@ typedef enum @property (nonatomic, assign) TGPhotoEditorBackButton backButtonType; @property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType; -- (instancetype)initWithBackButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground; +- (instancetype)initWithContext:(id)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground; - (void)transitionInAnimated:(bool)animated; - (void)transitionInAnimated:(bool)animated transparent:(bool)transparent; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index a12c65f1e2..6bfaaaadb0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -343,13 +343,13 @@ _captionMixin.stickersContext = stickersContext; [_captionMixin createInputPanelIfNeeded]; - _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false]; + _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false]; _portraitToolbarView.cancelPressed = toolbarCancelPressed; _portraitToolbarView.donePressed = toolbarDonePressed; _portraitToolbarView.doneLongPressed = toolbarDoneLongPressed; [_wrapperView addSubview:_portraitToolbarView]; - _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false]; + _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false]; _landscapeToolbarView.cancelPressed = toolbarCancelPressed; _landscapeToolbarView.donePressed = toolbarDonePressed; _landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m b/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m index cf5d6fc07f..5916515eb6 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerSendActionSheetController.m @@ -83,6 +83,8 @@ } - (void)buttonPressed { + _buttonView.enabled = false; + if (self.pressed != nil) self.pressed(); } @@ -310,6 +312,8 @@ } - (void)sendPressed { + _sendButton.enabled = false; + [self animateOut:false]; if (self.send != nil) diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index 1bba112b04..e2e4165e23 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -309,7 +309,7 @@ TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel; TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck; - _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:backButton doneButton:doneButton solidBackground:true]; + _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true]; [_portraitToolbarView setToolbarTabs:_availableTabs animated:false]; [_portraitToolbarView setActiveTab:_currentTab]; _portraitToolbarView.cancelPressed = toolbarCancelPressed; @@ -318,7 +318,7 @@ _portraitToolbarView.tabPressed = toolbarTabPressed; [_wrapperView addSubview:_portraitToolbarView]; - _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:backButton doneButton:doneButton solidBackground:true]; + _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true]; [_landscapeToolbarView setToolbarTabs:_availableTabs animated:false]; [_landscapeToolbarView setActiveTab:_currentTab]; _landscapeToolbarView.cancelPressed = toolbarCancelPressed; diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m index 1a3b0d64b2..77134e7bfe 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m @@ -12,6 +12,8 @@ @interface TGPhotoToolbarView () { + id _context; + UIView *_backgroundView; UIView *_buttonsWrapperView; @@ -28,11 +30,13 @@ @implementation TGPhotoToolbarView -- (instancetype)initWithBackButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground +- (instancetype)initWithContext:(id)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground { self = [super initWithFrame:CGRectZero]; if (self != nil) { + _context = context; + _interfaceOrientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation]; _backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; @@ -85,6 +89,10 @@ - (void)setDoneButtonType:(TGPhotoEditorDoneButton)doneButtonType { _doneButtonType = doneButtonType; + TGMediaAssetsPallete *pallete = nil; + if ([_context respondsToSelector:@selector(mediaAssetsPallete)]) + pallete = [_context mediaAssetsPallete]; + UIImage *doneImage; switch (doneButtonType) { @@ -94,19 +102,11 @@ case TGPhotoEditorDoneButtonDone: { - TGMediaAssetsPallete *pallete = nil; - if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) - pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete]; - doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]); break; } default: { - TGMediaAssetsPallete *pallete = nil; - if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) - pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete]; - doneImage = pallete != nil ? pallete.sendIconImage : TGComponentsImageNamed(@"PhotoPickerSendIcon"); } break; diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 166d6a1b89..4468f5dcb4 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -161,6 +161,7 @@ public enum MediaPlayerScrubbingNodeContent { private final class StandardMediaPlayerScrubbingNodeContentNode { let lineHeight: CGFloat let lineCap: MediaPlayerScrubbingNodeCap + let containerNode: ASDisplayNode let backgroundNode: ASImageNode let bufferingNode: MediaPlayerScrubbingBufferingNode let foregroundContentNode: ASImageNode @@ -172,9 +173,10 @@ private final class StandardMediaPlayerScrubbingNodeContentNode { let highlightedHandleNode: ASDisplayNode? let handleNodeContainer: MediaPlayerScrubbingNodeButton? - init(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, backgroundNode: ASImageNode, bufferingNode: MediaPlayerScrubbingBufferingNode, foregroundContentNode: ASImageNode, foregroundNode: MediaPlayerScrubbingForegroundNode, chapterNodesContainer: ASDisplayNode?, chapterNodes: [(MediaPlayerScrubbingChapter, ASDisplayNode)], handle: MediaPlayerScrubbingNodeHandle, handleNode: ASDisplayNode?, highlightedHandleNode: ASDisplayNode?, handleNodeContainer: MediaPlayerScrubbingNodeButton?) { + init(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, containerNode: ASDisplayNode, backgroundNode: ASImageNode, bufferingNode: MediaPlayerScrubbingBufferingNode, foregroundContentNode: ASImageNode, foregroundNode: MediaPlayerScrubbingForegroundNode, chapterNodesContainer: ASDisplayNode?, chapterNodes: [(MediaPlayerScrubbingChapter, ASDisplayNode)], handle: MediaPlayerScrubbingNodeHandle, handleNode: ASDisplayNode?, highlightedHandleNode: ASDisplayNode?, handleNodeContainer: MediaPlayerScrubbingNodeButton?) { self.lineHeight = lineHeight self.lineCap = lineCap + self.containerNode = containerNode self.backgroundNode = backgroundNode self.bufferingNode = bufferingNode self.foregroundContentNode = foregroundContentNode @@ -438,19 +440,21 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { chapterNodesContainer.isUserInteractionEnabled = false chapterNodesContainerImpl = chapterNodesContainer + var chapters = chapters + if let firstChapter = chapters.first, firstChapter.start > 0.0 { + chapters.insert(MediaPlayerScrubbingChapter(title: "", start: 0.0), at: 0) + } + for i in 0 ..< chapters.count { let chapterNode = ASDisplayNode() - chapterNode.backgroundColor = .black + chapterNode.backgroundColor = .white + chapterNodesContainer.addSubnode(chapterNode) - if i > 0 { - chapterNodesContainer.addSubnode(chapterNode) - } chapterNodes.append((chapters[i], chapterNode)) } } - - return .standard(StandardMediaPlayerScrubbingNodeContentNode(lineHeight: lineHeight, lineCap: lineCap, backgroundNode: backgroundNode, bufferingNode: bufferingNode, foregroundContentNode: foregroundContentNode, foregroundNode: foregroundNode, chapterNodesContainer: chapterNodesContainerImpl, chapterNodes: chapterNodes, handle: scrubberHandle, handleNode: handleNodeImpl, highlightedHandleNode: highlightedHandleNodeImpl, handleNodeContainer: handleNodeContainerImpl)) + return .standard(StandardMediaPlayerScrubbingNodeContentNode(lineHeight: lineHeight, lineCap: lineCap, containerNode: ASDisplayNode(), backgroundNode: backgroundNode, bufferingNode: bufferingNode, foregroundContentNode: foregroundContentNode, foregroundNode: foregroundNode, chapterNodesContainer: chapterNodesContainerImpl, chapterNodes: chapterNodes, handle: scrubberHandle, handleNode: handleNodeImpl, highlightedHandleNode: highlightedHandleNodeImpl, handleNodeContainer: handleNodeContainerImpl)) case let .custom(backgroundNode, foregroundContentNode): let foregroundNode = MediaPlayerScrubbingForegroundNode() foregroundNode.isLayerBacked = true @@ -507,14 +511,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { switch self.contentNodes { case let .standard(node): - self.addSubnode(node.backgroundNode) - self.addSubnode(node.bufferingNode) - node.foregroundNode.addSubnode(node.foregroundContentNode) - self.addSubnode(node.foregroundNode) + self.addSubnode(node.containerNode) - if let chapterNodesContainer = node.chapterNodesContainer { - self.addSubnode(chapterNodesContainer) - } + node.containerNode.addSubnode(node.backgroundNode) + node.containerNode.addSubnode(node.bufferingNode) + node.foregroundNode.addSubnode(node.foregroundContentNode) + node.containerNode.addSubnode(node.foregroundNode) if let handleNodeContainer = node.handleNodeContainer { self.addSubnode(handleNodeContainer) @@ -789,7 +791,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { let bounds = self.bounds var isPlaying = false - var timestampAndDuration: (timestamp: Double, duration: Double)? + var timestampAndDuration: (timestamp: Double?, duration: Double)? if let statusValue = self.statusValue { switch statusValue.status { case .playing: @@ -798,6 +800,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { break } if case .buffering(true, _, _, _) = statusValue.status { + timestampAndDuration = (nil, statusValue.duration) //initialBuffering = true } else if Double(0.0).isLess(than: statusValue.duration) { if let scrubbingTimestampValue = self.scrubbingTimestampValue { @@ -819,6 +822,8 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { switch self.contentNodes { case let .standard(node): + node.containerNode.frame = CGRect(origin: CGPoint(), size: bounds.size) + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight)) node.backgroundNode.position = backgroundFrame.center node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size) @@ -827,24 +832,50 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { node.foregroundContentNode.position = foregroundContentFrame.center node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size) - node.bufferingNode.frame = backgroundFrame node.bufferingNode.updateLayout(size: backgroundFrame.size, transition: .immediate) - if let chapterNodesContainer = node.chapterNodesContainer, let duration = timestampAndDuration?.duration, duration > 0.0, backgroundFrame.width > 0.0 { - chapterNodesContainer.frame = backgroundFrame - - for i in 0 ..< node.chapterNodes.count { - let (chapter, chapterNode) = node.chapterNodes[i] - if i == 0 || chapter.start > duration { - continue + if let chapterNodesContainer = node.chapterNodesContainer { + if let duration = timestampAndDuration?.duration, duration > 1.0, backgroundFrame.width > 0.0, node.chapterNodes.count > 1 { + if node.containerNode.view.mask == nil { + node.containerNode.view.mask = chapterNodesContainer.view + + let transitionView = UIView() + transitionView.backgroundColor = .white + transitionView.frame = node.containerNode.bounds + chapterNodesContainer.view.addSubview(transitionView) + transitionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionView] _ in + transitionView?.removeFromSuperview() + }) } - let chapterPosition: CGFloat = floor(backgroundFrame.width * CGFloat(chapter.start / duration)) - let chapterLineWidth: CGFloat = 1.5 - chapterNode.frame = CGRect(x: chapterPosition - chapterLineWidth / 2.0, y: 0.0, width: chapterLineWidth, height: backgroundFrame.size.height) + + chapterNodesContainer.frame = backgroundFrame + + for i in 1 ..< node.chapterNodes.count { + let (previousChapter, previousChapterNode) = node.chapterNodes[i - 1] + let (chapter, chapterNode) = node.chapterNodes[i] + + let lineWidth: CGFloat = 1.0 + UIScreenPixel * 2.0 + let startPosition: CGFloat + if i == 1 { + startPosition = 0.0 + } else { + startPosition = floor(backgroundFrame.width * CGFloat(previousChapter.start / duration)) + lineWidth / 2.0 + } + let endPosition: CGFloat = max(startPosition, floor(backgroundFrame.width * CGFloat(chapter.start / duration)) - lineWidth / 2.0) + previousChapterNode.frame = CGRect(x: startPosition, y: 0.0, width: endPosition - startPosition, height: backgroundFrame.size.height) + + if i == node.chapterNodes.count - 1 { + let startPosition = endPosition + lineWidth + chapterNode.frame = CGRect(x: startPosition, y: 0.0, width: backgroundFrame.size.width - startPosition, height: backgroundFrame.size.height) + } + } + } else { + node.containerNode.view.mask = nil } } + if let handleNode = node.handleNode { var handleSize: CGSize = CGSize(width: 2.0, height: bounds.size.height) var handleOffset: CGFloat = 0.0 @@ -863,7 +894,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { handleNodeContainer.frame = bounds } - if let (timestamp, duration) = timestampAndDuration { + if let (maybeTimestamp, duration) = timestampAndDuration, let timestamp = maybeTimestamp, duration > 0.01 { if let scrubbingTimestampValue = self.scrubbingTimestampValue { var progress = CGFloat(scrubbingTimestampValue / duration) if progress.isNaN || !progress.isFinite { diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index 1ff524792d..366b0a84ff 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -13,8 +13,8 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import PresentationDataUtils -private func shareQrCode(context: AccountContext, link: String, view: UIView) { - let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo"))) +private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) { + let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl) |> map { _, generator -> UIImage? in let imageSize = CGSize(width: 768.0, height: 768.0) let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0)) @@ -47,6 +47,15 @@ public final class QrCodeScreen: ViewController { return invite.link } } + + var ecl: String { + switch self { + case .peer: + return "Q" + case .invite: + return "Q" + } + } } private var controllerNode: Node { @@ -293,11 +302,11 @@ public final class QrCodeScreen: ViewController { self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) self.buttonNode.pressed = { [weak self] in if let strongSelf = self{ - shareQrCode(context: strongSelf.context, link: subject.link, view: strongSelf.view) + shareQrCode(context: strongSelf.context, link: subject.link, ecl: subject.ecl, view: strongSelf.view) } } - self.qrImageNode.setSignal(qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: .cutout) |> beforeNext { [weak self] size, _ in + self.qrImageNode.setSignal(qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: subject.ecl) |> beforeNext { [weak self] size, _ in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json new file mode 100644 index 0000000000..204c66f291 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Snowflake.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png b/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..f781e571612bfa9fdef756e6f9526bc4461cd55b GIT binary patch literal 5794 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 literal 0 HcmV?d00001 diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index 4f7c609271..2ff847f04a 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -309,7 +309,7 @@ public func addLocallyGeneratedEntities(_ text: String, enabledTypes: EnabledEnt } } - if hasDigits { + if hasDigits || hasColons { if let phoneNumberDetector = phoneNumberDetector, detectPhoneNumbers { let utf16 = text.utf16 phoneNumberDetector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 453b6ca4e5..1297e5144a 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -431,6 +431,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return self._isReady.get() } + private var newYearNode: WallpaperNewYearNode? + init(context: AccountContext, useSharedAnimationPhase: Bool) { self.context = context self.useSharedAnimationPhase = useSharedAnimationPhase @@ -450,7 +452,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.contentNode.frame = self.bounds self.addSubnode(self.contentNode) self.addSubnode(self.patternImageNode) - + //self.view.addSubview(self.bakedBackgroundView) } @@ -793,10 +795,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } self.loadPatternForSizeIfNeeded(size: size, transition: transition) - + if isFirstLayout && !self.frame.isEmpty { self.updateScale() + + if false, self.newYearNode == nil { + let newYearNode = WallpaperNewYearNode() + self.addSubnode(newYearNode) + self.newYearNode = newYearNode + } } + + self.newYearNode?.frame = CGRect(origin: CGPoint(), size: size) + self.newYearNode?.updateLayout(size: size) } func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { @@ -1695,3 +1706,47 @@ public func createWallpaperBackgroundNode(context: AccountContext, forChatDispla return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase) } + +private class WallpaperNewYearNode: ASDisplayNode { + private var emitterLayer: CAEmitterLayer? + + func updateLayout(size: CGSize) { + if self.emitterLayer == nil { + let particlesLayer = CAEmitterLayer() + self.emitterLayer = particlesLayer + + self.layer.addSublayer(particlesLayer) + self.layer.masksToBounds = true + + particlesLayer.backgroundColor = UIColor.clear.cgColor + particlesLayer.emitterShape = .circle + particlesLayer.emitterMode = .surface + particlesLayer.renderMode = .oldestLast + + let cell1 = CAEmitterCell() + cell1.contents = UIImage(bundleImageName: "Components/Snowflake")?.cgImage + cell1.name = "snow" + cell1.birthRate = 352.0 + cell1.lifetime = 20.0 + cell1.velocity = 39.0 + cell1.velocityRange = -15.0 + cell1.xAcceleration = 5.0 + cell1.yAcceleration = 25.0 + cell1.emissionRange = .pi + cell1.spin = -28.6 * (.pi / 180.0) + cell1.spinRange = 57.2 * (.pi / 180.0) + cell1.scale = 0.04 + cell1.scaleRange = 0.15 + cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor + cell1.alphaRange = -0.2 + + particlesLayer.emitterCells = [cell1] + } + + if let emitterLayer = self.emitterLayer { + emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: -size.height / 2.0) + emitterLayer.emitterSize = CGSize(width: size.width * 2.5, height: size.height) + emitterLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) + } + } +}