diff --git a/Telegram/Telegram-iOS/Resources/Gift.tgs b/Telegram/Telegram-iOS/Resources/Gift.tgs new file mode 100644 index 0000000000..f0b6f7a3cb Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Gift.tgs differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d682f4bf7d..3c0613cc7c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7831,6 +7831,9 @@ Sorry for the inconvenience."; "Notification.PremiumGift.Sent" = "%1$@ sent you a gift for %2$@"; "Notification.PremiumGift.SentYou" = "You sent a gift for %@"; +"Notification.PremiumGift.Months_1" = "%@ month"; +"Notification.PremiumGift.Months_any" = "%@ months"; + "Notification.PremiumGift.Title" = "Telegram Premium"; "Notification.PremiumGift.Subtitle" = "for %@"; "Notification.PremiumGift.View" = "View"; diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index dba478408d..f0d487f5cd 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -147,10 +147,15 @@ private let moreButtonImage = generateTintedImage(image: UIImage(bundleImageName private let placeholderFont = Font.regular(16.0) private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode { + enum Mode { + case pictureInPicture + case airplay + } + private let iconNode: ASImageNode private let textNode: ASTextNode - init(strings: PresentationStrings) { + init(strings: PresentationStrings, mode: Mode) { self.iconNode = ASImageNode() self.iconNode.isLayerBacked = true self.iconNode.displayWithoutProcessing = true @@ -160,10 +165,20 @@ private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode self.textNode = ASTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false - self.textNode.attributedText = NSAttributedString(string: strings.Embed_PlayingInPIP, font: placeholderFont, textColor: UIColor(rgb: 0x8e8e93)) + + let text: String + switch mode { + case .pictureInPicture: + text = strings.Embed_PlayingInPIP + case .airplay: + text = strings.Gallery_AirPlayPlaceholder + } + self.textNode.attributedText = NSAttributedString(string: text, font: placeholderFont, textColor: UIColor(rgb: 0x8e8e93)) super.init() + self.backgroundColor = UIColor(rgb: 0x333335) + self.addSubnode(self.iconNode) self.addSubnode(self.textNode) } @@ -975,7 +990,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let pictureInPictureNode = self.pictureInPictureNode { if let item = self.item { - let placeholderSize = item.content.dimensions.fitted(layout.size) + var placeholderSize = item.content.dimensions.fitted(layout.size) + placeholderSize.height += 2.0 transition.updateFrame(node: pictureInPictureNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - placeholderSize.width) / 2.0), y: floor((layout.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) pictureInPictureNode.updateLayout(placeholderSize, transition: transition) } @@ -1144,11 +1160,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.videoNode = videoNode self.videoNodeUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction videoNode.isUserInteractionEnabled = disablePlayerControls || forceEnableUserInteraction - videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) + videoNode.backgroundColor = UIColor.black if item.fromPlayingVideo { videoNode.canAttachContent = false } else { - self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + self.updateDisplayPlaceholder() } scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in @@ -1492,21 +1508,25 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.videoNode?.notifyPlaybackControlsHidden(!isVisible) } + private func updateDisplayPlaceholder() { + self.updateDisplayPlaceholder(!(self.videoNode?.ownsContentNode ?? true) || self.isAirPlayActive) + } + private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) { if displayPlaceholder && !self.disablePictureInPicturePlaceholder { if self.pictureInPictureNode == nil { - let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings) + let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings, mode: self.isAirPlayActive ? .airplay : .pictureInPicture) pictureInPictureNode.isUserInteractionEnabled = false self.pictureInPictureNode = pictureInPictureNode self.insertSubnode(pictureInPictureNode, aboveSubnode: self.scrollNode) if let validLayout = self.validLayout { if let item = self.item { - let placeholderSize = item.content.dimensions.fitted(validLayout.0.size) - pictureInPictureNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.0.size.width - placeholderSize.width) / 2.0), y: floor((validLayout.0.size.height - placeholderSize.height) / 2.0)), size: placeholderSize) + var placeholderSize = item.content.dimensions.fitted(validLayout.0.size) + placeholderSize.height += 2.0 + pictureInPictureNode.frame = CGRect(origin: CGPoint(x: floor((validLayout.0.size.width - placeholderSize.width) / 2.0), y: floorToScreenPixels((validLayout.0.size.height - placeholderSize.height) / 2.0)), size: placeholderSize) pictureInPictureNode.updateLayout(placeholderSize, transition: .immediate) } } - self.videoNode?.backgroundColor = UIColor(rgb: 0x333335) } } else if let pictureInPictureNode = self.pictureInPictureNode { self.pictureInPictureNode = nil @@ -1602,10 +1622,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } else { videoNode.continuePlayingWithoutSound() } - self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + self.updateDisplayPlaceholder() } else if !item.fromPlayingVideo { videoNode.canAttachContent = isVisible - self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + self.updateDisplayPlaceholder() } if self.shouldAutoplayOnCentrality() { self.hideStatusNodeUntilCentrality = true @@ -1690,7 +1710,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) videoNode.canAttachContent = true - self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + self.updateDisplayPlaceholder() self.context.sharedContext.mediaManager.setOverlayVideoNode(nil) } else { @@ -1768,7 +1788,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if self.item?.fromPlayingVideo ?? false { Queue.mainQueue().after(0.001) { videoNode.canAttachContent = true - self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + self.updateDisplayPlaceholder() } } @@ -2457,6 +2477,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) }))) + if #available(iOS 11.0, *) { + items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + guard let strongSelf = self else { + return + } + strongSelf.beginAirPlaySetup() + }))) + } + if let (message, _, _) = strongSelf.contentInfo() { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { @@ -2578,63 +2608,85 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return items } } + + private var isAirPlayActive = false + private var externalVideoPlayer: ExternalVideoPlayer? + func beginAirPlaySetup() { + guard let content = self.item?.content as? NativeVideoContent else { + return + } + if #available(iOS 11.0, *) { + self.externalVideoPlayer = ExternalVideoPlayer(context: self.context, content: content) + self.externalVideoPlayer?.openRouteSelection() + self.externalVideoPlayer?.isActiveUpdated = { [weak self] isActive in + if let strongSelf = self { + if strongSelf.isAirPlayActive && !isActive { + strongSelf.externalVideoPlayer = nil + } + strongSelf.isAirPlayActive = isActive + strongSelf.updateDisplayPlaceholder() + } + } + } + } @objc func openStickersButtonPressed() { - if let content = self.item?.content as? NativeVideoContent { - let context = self.context - let media = content.fileReference.abstract - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let topController = (self.baseNavigationController()?.topViewController as? ViewController) - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - topController?.present(controller, in: .window(.root), with: nil) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - self.isInteractingPromise.set(true) - - let signal = self.context.engine.stickers.stickerPacksAttachedToMedia(media: media) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let _ = (signal - |> deliverOnMainQueue).start(next: { [weak self] packs in - guard let strongSelf = self, !packs.isEmpty else { - return - } - let baseNavigationController = strongSelf.baseNavigationController() - baseNavigationController?.view.endEditing(true) - let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { info, items, action in - let animateInAsReplacement = false - switch action { - case .add: - topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in - return true - }), in: .window(.root)) - case let .remove(positionInList): - topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in - if case .undo = action { - let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start() - } - return true - }), in: .window(.root)) - } - }, dismissed: { [weak self] in - self?.isInteractingPromise.set(false) - }) - (baseNavigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root), with: nil) - }) + guard let content = self.item?.content as? NativeVideoContent else { + return } + let context = self.context + let media = content.fileReference.abstract + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let topController = (self.baseNavigationController()?.topViewController as? ViewController) + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + topController?.present(controller, in: .window(.root), with: nil) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + self.isInteractingPromise.set(true) + + let signal = self.context.engine.stickers.stickerPacksAttachedToMedia(media: media) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak self] packs in + guard let strongSelf = self, !packs.isEmpty else { + return + } + let baseNavigationController = strongSelf.baseNavigationController() + baseNavigationController?.view.endEditing(true) + let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { info, items, action in + let animateInAsReplacement = false + switch action { + case .add: + topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in + return true + }), in: .window(.root)) + case let .remove(positionInList): + topController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in + if case .undo = action { + let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start() + } + return true + }), in: .window(.root)) + } + }, dismissed: { [weak self] in + self?.isInteractingPromise.set(false) + }) + (baseNavigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root), with: nil) + }) } override func adjustForPreviewing() { diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 26d5e627a9..a1188dd1ef 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -799,11 +799,11 @@ private final class PremiumGiftScreenComponent: CombinedComponent { let duration: Int32 switch product.id { case "org.telegram.telegramPremium.twelveMonths": - duration = 86400 * 365 + duration = 12 case "org.telegram.telegramPremium.sixMonths": - duration = 86400 * 180 + duration = 6 case "org.telegram.telegramPremium.threeMonths": - duration = 86400 * 90 + duration = 3 default: duration = 0 } @@ -1259,13 +1259,16 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer { } completionImpl = { [weak self] duration in - if let strongSelf = self { - let navigationController = strongSelf.navigationController - strongSelf.dismiss() - let introController = PremiumIntroScreen(context: context, source: .gift(from: context.account.peerId, to: peerId, duration: duration)) - navigationController?.pushViewController(introController, animated: true) + if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { +// let introController = PremiumIntroScreen(context: context, source: .gift(from: context.account.peerId, to: peerId, duration: duration)) + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) } + navigationController.setViewControllers(controllers, animated: true) + Queue.mainQueue().after(0.1, { - introController.view.addSubview(ConfettiView(frame: introController.view.bounds)) + if let topController = navigationController.viewControllers.first { + topController.view.addSubview(ConfettiView(frame: topController.view.bounds)) + } }) } } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 44ff71410a..1d38ee523a 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1487,21 +1487,21 @@ private final class PremiumIntroScreenComponent: CombinedComponent { secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string } else if case let .gift(fromPeerId, _, duration) = context.component.source { if fromPeerId == context.component.context.account.peerId { - if duration >= 86400 * 365 { + if duration == 12 { secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string - } else if duration >= 86400 * 180 { + } else if duration == 6 { secondaryTitleText = environment.strings.Premium_GiftedTitleYou_6Month(otherPeerName).string - } else if duration >= 86400 * 90 { + } else if duration == 3 { secondaryTitleText = environment.strings.Premium_GiftedTitleYou_3Month(otherPeerName).string } else { secondaryTitleText = "" } } else { - if duration >= 86400 * 365 { + if duration == 12 { secondaryTitleText = environment.strings.Premium_GiftedTitle_12Month(otherPeerName).string - } else if duration >= 86400 * 180 { + } else if duration == 6 { secondaryTitleText = environment.strings.Premium_GiftedTitle_6Month(otherPeerName).string - } else if duration >= 86400 * 90 { + } else if duration == 3 { secondaryTitleText = environment.strings.Premium_GiftedTitle_3Month(otherPeerName).string } else { secondaryTitleText = "" diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 930de2caa4..e9992927ac 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -1190,8 +1190,21 @@ public final class MediaStreamComponentController: ViewControllerComponentContai view.expandFromPictureInPicture() } + if let validLayout = self.validLayout { + self.view.clipsToBounds = true + self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius + if #available(iOS 13.0, *) { + self.view.layer.cornerCurve = .continuous + } + + self.view.layer.animatePosition(from: CGPoint(x: 353.0, y: 117.0), to: self.view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in + self?.view.layer.cornerRadius = 0.0 + }) + self.view.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } + self.view.layer.allowsGroupOpacity = true - self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in + self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in guard let strongSelf = self else { return } @@ -1226,6 +1239,18 @@ public final class MediaStreamComponentController: ViewControllerComponentContai strongSelf.view.layer.allowsGroupOpacity = false strongSelf.dismissImpl(completion: completion) }) + + if let validLayout = self.validLayout { + self.view.clipsToBounds = true + self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius + if #available(iOS 13.0, *) { + self.view.layer.cornerCurve = .continuous + } + + self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: 353.0, y: 117.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in + }) + self.view.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } } private func dismissImpl(completion: (() -> Void)? = nil) { diff --git a/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/Contents.json deleted file mode 100644 index 74c463b66f..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "gift.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/gift.png b/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/gift.png deleted file mode 100644 index a31babda90..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Components/Gift.imageset/gift.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Airplay.pdf b/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Airplay.pdf new file mode 100644 index 0000000000..756e9d0e9a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Airplay.pdf @@ -0,0 +1,188 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.335022 2.334961 cm +0.000000 0.000000 0.000000 scn +3.865000 17.330078 m +3.837526 17.330078 l +3.300828 17.330086 2.857980 17.330093 2.497252 17.300621 c +2.122623 17.270012 1.778399 17.204330 1.455116 17.039610 c +0.953664 16.784107 0.545970 16.376415 0.290468 15.874963 c +0.125747 15.551680 0.060066 15.207455 0.029457 14.832827 c +-0.000016 14.472098 -0.000009 14.029249 0.000000 13.492552 c +0.000000 13.465077 l +0.000000 7.865078 l +0.000000 7.837605 l +-0.000009 7.300907 -0.000016 6.858058 0.029457 6.497330 c +0.060066 6.122702 0.125747 5.778477 0.290468 5.455194 c +0.545970 4.953741 0.953664 4.546048 1.455116 4.290545 c +1.778399 4.125825 2.122623 4.060143 2.497252 4.029535 c +2.857977 4.000063 3.300821 4.000070 3.837511 4.000078 c +3.837541 4.000078 l +3.865000 4.000078 l +4.665000 4.000078 l +5.032270 4.000078 5.330000 4.297809 5.330000 4.665078 c +5.330000 5.032348 5.032270 5.330078 4.665000 5.330078 c +3.865000 5.330078 l +3.293975 5.330078 2.905699 5.330595 2.605556 5.355118 c +2.313176 5.379006 2.163463 5.422318 2.058923 5.475584 c +1.807727 5.603576 1.603498 5.807804 1.475507 6.059001 c +1.422241 6.163542 1.378929 6.313254 1.355040 6.605635 c +1.330518 6.905777 1.330000 7.294052 1.330000 7.865078 c +1.330000 13.465077 l +1.330000 14.036103 1.330518 14.424378 1.355040 14.724522 c +1.378929 15.016902 1.422241 15.166615 1.475507 15.271155 c +1.603498 15.522351 1.807727 15.726581 2.058923 15.854571 c +2.163463 15.907838 2.313176 15.951150 2.605556 15.975038 c +2.905699 15.999560 3.293974 16.000078 3.865000 16.000078 c +15.465000 16.000078 l +16.036026 16.000078 16.424301 15.999560 16.724445 15.975038 c +17.016823 15.951150 17.166538 15.907838 17.271076 15.854571 c +17.522274 15.726581 17.726501 15.522351 17.854492 15.271155 c +17.907761 15.166615 17.951073 15.016902 17.974960 14.724522 c +17.999481 14.424378 18.000000 14.036104 18.000000 13.465078 c +18.000000 7.865078 l +18.000000 7.294053 17.999481 6.905778 17.974960 6.605635 c +17.951073 6.313254 17.907761 6.163542 17.854492 6.059001 c +17.726501 5.807804 17.522274 5.603576 17.271076 5.475584 c +17.166538 5.422318 17.016823 5.379006 16.724445 5.355118 c +16.424301 5.330595 16.036026 5.330078 15.465000 5.330078 c +14.665000 5.330078 l +14.297730 5.330078 14.000000 5.032348 14.000000 4.665078 c +14.000000 4.297809 14.297730 4.000078 14.665000 4.000078 c +15.465000 4.000078 l +15.492459 4.000078 l +15.492488 4.000078 l +16.029179 4.000070 16.472023 4.000063 16.832748 4.029535 c +17.207378 4.060143 17.551601 4.125825 17.874886 4.290545 c +18.376335 4.546048 18.784031 4.953741 19.039532 5.455194 c +19.204254 5.778477 19.269936 6.122702 19.300545 6.497330 c +19.330015 6.858045 19.330009 7.300875 19.330002 7.837547 c +19.330002 7.837619 l +19.330002 7.865078 l +19.330002 13.465078 l +19.330002 13.492537 l +19.330002 13.492610 l +19.330009 14.029282 19.330015 14.472111 19.300545 14.832827 c +19.269936 15.207455 19.204254 15.551680 19.039532 15.874963 c +18.784031 16.376415 18.376335 16.784107 17.874886 17.039610 c +17.551601 17.204330 17.207378 17.270012 16.832748 17.300621 c +16.472019 17.330093 16.029171 17.330086 15.492474 17.330078 c +15.465000 17.330078 l +3.865000 17.330078 l +h +9.549997 5.417668 m +9.624264 5.444814 9.705738 5.444814 9.780005 5.417668 c +9.779352 5.417907 9.779119 5.418036 9.779340 5.417960 c +9.779449 5.417922 9.779672 5.417834 9.780010 5.417685 c +9.785703 5.415166 9.824282 5.395164 9.915651 5.303084 c +10.027624 5.190243 10.164604 5.026791 10.383287 4.764371 c +11.967221 2.863649 l +12.324518 2.434894 12.562548 2.148232 12.716402 1.925475 c +12.838650 1.748481 12.861221 1.674737 12.864748 1.663215 c +12.864949 1.662560 l +12.864219 1.564913 12.820898 1.472416 12.746351 1.409344 c +12.745722 1.409081 l +12.734627 1.404418 12.663531 1.374542 12.449274 1.355145 c +12.179653 1.330734 11.807049 1.330078 11.248934 1.330078 c +8.081068 1.330078 l +7.522953 1.330078 7.150349 1.330734 6.880728 1.355145 c +6.666473 1.374543 6.595378 1.404416 6.584280 1.409080 c +6.583652 1.409343 l +6.509105 1.472416 6.465783 1.564913 6.465053 1.662560 c +6.465254 1.663213 l +6.468780 1.674733 6.491349 1.748475 6.613600 1.925474 c +6.767454 2.148230 7.005483 2.434893 7.362779 2.863647 c +8.946714 4.764370 l +9.165398 5.026790 9.302377 5.190243 9.414351 5.303084 c +9.505718 5.395163 9.544298 5.415166 9.549992 5.417685 c +9.551012 5.418137 9.550975 5.418026 9.549997 5.417668 c +h +10.236588 6.666842 m +9.867472 6.801756 9.462530 6.801756 9.093414 6.666842 c +8.834289 6.572128 8.636753 6.407670 8.470269 6.239893 c +8.312561 6.080961 8.138463 5.872022 7.942029 5.636275 c +7.924980 5.615815 l +6.341045 3.715093 l +6.320368 3.690281 l +5.989172 3.292866 5.711553 2.959741 5.519254 2.681324 c +5.330824 2.408508 5.141049 2.075929 5.135232 1.690181 c +5.127517 1.178505 5.355523 0.691704 5.753542 0.370064 c +6.053606 0.127583 6.430592 0.060459 6.760806 0.030563 c +7.097775 0.000055 7.531381 0.000065 8.048665 0.000076 c +8.048759 0.000076 l +8.081068 0.000076 l +11.248934 0.000076 l +11.281242 0.000076 l +11.281337 0.000076 l +11.798622 0.000065 12.232226 0.000055 12.569197 0.030563 c +12.899410 0.060459 13.276397 0.127583 13.576460 0.370064 c +13.974480 0.691704 14.202486 1.178505 14.194770 1.690181 c +14.188953 2.075929 13.999178 2.408508 13.810747 2.681325 c +13.618445 2.959745 13.340822 3.292877 13.009618 3.690300 c +12.988955 3.715096 l +11.405022 5.615816 l +11.387974 5.636274 l +11.191540 5.872021 11.017441 6.080961 10.859733 6.239894 c +10.693249 6.407670 10.495712 6.572128 10.236588 6.666842 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 5505 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000005595 00000 n +0000005618 00000 n +0000005791 00000 n +0000005865 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5924 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Contents.json new file mode 100644 index 0000000000..458cfc4966 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/AirPlay.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Airplay.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift index db3f927777..c498faa0cc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift @@ -14,6 +14,8 @@ import UrlEscaping import TelegramStringFormatting import WallpaperBackgroundNode import ReactionSelectionNode +import AnimatedStickerNode +import TelegramAnimatedStickerNode private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false) @@ -29,8 +31,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let mediaBackgroundNode: NavigationBackgroundNode private let titleNode: TextNode private let subtitleNode: TextNode - - private let giftNode: ASImageNode + private let animationNode: AnimatedStickerNode private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode @@ -62,10 +63,10 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.clipsToBounds = true self.buttonNode.cornerRadius = 17.0 - - self.giftNode = ASImageNode() - self.giftNode.isUserInteractionEnabled = false - self.giftNode.displaysAsynchronously = false + + self.animationNode = DefaultAnimatedStickerNodeImpl() + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Gift"), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + self.animationNode.visibility = true self.buttonStarsNode = PremiumStarsNode() @@ -80,7 +81,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.mediaBackgroundNode) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) - self.addSubnode(self.giftNode) + self.addSubnode(self.animationNode) self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) @@ -128,7 +129,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in - let imageSize = CGSize(width: 220.0, height: 210.0) + let giftSize = CGSize(width: 220.0, height: 240.0) let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId) @@ -139,7 +140,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if let action = media as? TelegramMediaAction { switch action.action { case let .giftPremium(_, _, durationValue): - duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(timeIntervalString(strings: item.presentationData.strings, value: durationValue)).string + duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(durationValue)).string default: break } @@ -148,11 +149,11 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_Title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_Title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: duration, font: Font.regular(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: duration, font: Font.regular(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) var labelRects = labelLayout.linesRects() if labelRects.count > 1 { @@ -184,7 +185,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { backgroundMaskUpdated = true } - let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + imageSize.height + 18.0) + let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + giftSize.height + 18.0) return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in @@ -193,7 +194,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper) - let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 16.0), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: labelLayout.size.height + 16.0), size: giftSize) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame @@ -201,10 +202,9 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) - strongSelf.giftNode.image = UIImage(bundleImageName: "Components/Gift") - if let image = strongSelf.giftNode.image { - strongSelf.giftNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - image.size.width) / 2.0), y: mediaBackgroundFrame.minY + 14.0), size: image.size) - } + let iconSize = CGSize(width: 160.0, height: 160.0) + strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0), size: iconSize) + strongSelf.animationNode.updateLayout(size: iconSize) let _ = labelApply() let _ = titleApply() @@ -214,7 +214,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: 2.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame - let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 121.0), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY - 1.0), size: subtitleLayout.size) diff --git a/submodules/TelegramUniversalVideoContent/Sources/ExternalVideoPlayer.swift b/submodules/TelegramUniversalVideoContent/Sources/ExternalVideoPlayer.swift new file mode 100644 index 0000000000..eceba5b1dd --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/Sources/ExternalVideoPlayer.swift @@ -0,0 +1,121 @@ +import Foundation +import AVFoundation +import SwiftSignalKit +import UniversalMediaPlayer +import AccountContext +import AVKit + +public class ExternalVideoPlayer: NSObject, AVRoutePickerViewDelegate { + private let context: AccountContext + let content: NativeVideoContent + + let player: AVPlayer? + private var didPlayToEndTimeObserver: NSObjectProtocol? + private var timeObserver: Any? + + private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: false, progress: 0.0, display: true), soundEnabled: true) + private let _status = ValuePromise() + var status: Signal { + return self._status.get() + } + private var seekId: Int = 0 + + private weak var routePickerView: UIView? + + public var isActiveUpdated: (Bool) -> Void = { _ in } + + public init(context: AccountContext, content: NativeVideoContent) { + self.context = context + self.content = content + + if let path = context.account.postbox.mediaBox.completedResourcePath(content.fileReference.media.resource, pathExtension: "mp4") { + let player = AVPlayer(url: URL(fileURLWithPath: path)) + self.player = player + } else { + self.player = nil + } + + super.init() + + self.startObservingForAirPlayStatusChanges() + self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false) + + if let player = self.player { + self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil, using: { [weak self] notification in + if let strongSelf = self { + strongSelf.player?.seek(to: CMTime(seconds: 0.0, preferredTimescale: 30)) + strongSelf.play() + } + }) + + self.timeObserver = player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 10), queue: DispatchQueue.main) { [weak self] time in + guard let strongSelf = self else { + return + } + strongSelf.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: strongSelf.statusValue.duration, dimensions: CGSize(), timestamp: CMTimeGetSeconds(time), baseRate: 1.0, seekId: strongSelf.seekId, status: strongSelf.statusValue.status, soundEnabled: true) + strongSelf._status.set(strongSelf.statusValue) + } + } + + self._status.set(self.statusValue) + } + + deinit { + if let timeObserver = self.timeObserver { + self.player?.removeTimeObserver(timeObserver) + } + + if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { + NotificationCenter.default.removeObserver(didPlayToEndTimeObserver) + } + + self.stopObservingForAirPlayStatusChanges() + } + + public func play() { + self.player?.play() + } + + public func openRouteSelection() { + if #available(iOS 11.0, *) { + let routePickerView = AVRoutePickerView() + routePickerView.delegate = self + if #available(iOS 13.0, *) { + routePickerView.prioritizesVideoDevices = true + } + self.context.sharedContext.mainWindow?.viewController?.view.addSubview(routePickerView) + + if let routePickerButton = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton { + routePickerButton.sendActions(for: .touchUpInside) + } + } + } + + @available(iOS 11.0, *) + public func routePickerViewDidEndPresentingRoutes(_ routePickerView: AVRoutePickerView) { + routePickerView.removeFromSuperview() + + self.play() + } + + private var observerContextAirplay = 1 + + func startObservingForAirPlayStatusChanges() + { + self.player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive), options: .new, context: &observerContextAirplay) + } + + func stopObservingForAirPlayStatusChanges() + { + self.player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.isExternalPlaybackActive)) + } + + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if context == &observerContextAirplay { + self.isActiveUpdated(self.player?.isExternalPlaybackActive ?? false) + } + else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + } + } +} diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index dbf561cd29..e75e08da6c 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -777,6 +777,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } self.controller?.present(alertController, in: .window(.root)) } + case "web_app_setup_closing_behavior": + if let json = json, let _ = json["need_confirmation"] as? String { + + } default: break }