From 5877f2c20dd70681c70c33aadd3913a24706b344 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 29 Oct 2025 17:20:36 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 + .../Sources/AccountContext.swift | 6 +- .../Sources/AttachmentContainer.swift | 14 +- .../Sources/AttachmentPanel.swift | 4 +- submodules/Camera/Sources/Camera.swift | 134 +- submodules/Camera/Sources/CameraOutput.swift | 4 +- .../Sources/ComposePollScreen.swift | 2 +- .../Sources/ContactsController.swift | 43 +- .../Sources/ContactsSearchContainerNode.swift | 7 +- ...onSequenceCountrySelectionController.swift | 6 +- ...quenceCountrySelectionControllerNode.swift | 18 +- .../Display/Source/NativeWindowHostView.swift | 13 +- .../Navigation/NavigationContainer.swift | 4 +- .../Display/Source/ViewController.swift | 6 +- .../Sources/TGIconSwitchView.m | 14 +- .../Sources/ListMessageFileItemNode.swift | 2 +- .../LocationPickerControllerNode.swift | 6 +- .../Sources/MediaPickerScreen.swift | 8 +- .../CreateExternalMediaStreamScreen.swift | 51 +- .../Sources/UserInfoController.swift | 33 +- submodules/TelegramApi/Sources/Api0.swift | 21 +- submodules/TelegramApi/Sources/Api1.swift | 102 +- submodules/TelegramApi/Sources/Api10.swift | 40 + submodules/TelegramApi/Sources/Api15.swift | 14 +- submodules/TelegramApi/Sources/Api2.swift | 104 +- submodules/TelegramApi/Sources/Api25.swift | 420 +-- submodules/TelegramApi/Sources/Api26.swift | 634 ++-- submodules/TelegramApi/Sources/Api27.swift | 418 +++ submodules/TelegramApi/Sources/Api3.swift | 46 + submodules/TelegramApi/Sources/Api36.swift | 144 +- submodules/TelegramApi/Sources/Api37.swift | 86 + submodules/TelegramApi/Sources/Api39.swift | 39 +- submodules/TelegramApi/Sources/Api7.swift | 20 +- .../GroupCallNavigationAccessoryPanel.swift | 2 +- .../Sources/PresentationGroupCall.swift | 4 + .../Account/AccountIntermediateState.swift | 30 +- .../ApiUtils/TelegramMediaAction.swift | 4 +- .../State/AccountStateManagementUtils.swift | 20 +- .../Sources/State/AccountStateManager.swift | 83 + .../Sources/State/Serialization.swift | 2 +- .../SyncCore_TelegramMediaAction.swift | 8 +- .../TelegramEngine/Calls/GroupCalls.swift | 10 +- .../TelegramEngine/Messages/Stories.swift | 18 +- .../Messages/TelegramEngineMessages.swift | 4 +- .../Payments/BotPaymentForm.swift | 21 +- .../TelegramEngine/Payments/StarGifts.swift | 4 + .../Payments/StarGiftsAuctions.swift | 207 ++ .../TelegramEngine/Payments/Stars.swift | 2 +- .../Sources/AnimatedTextComponent.swift | 16 +- .../Sources/AttachmentFileSearchItem.swift | 6 +- .../Sources/ButtonComponent.swift | 2 +- .../TelegramUI/Components/CameraScreen/BUILD | 1 + .../Sources/CameraLiveStreamComponent.swift | 139 +- .../CameraScreen/Sources/CameraScreen.swift | 389 ++- .../Sources/CameraVideoSource.swift | 164 + .../Sources/CaptureControlsComponent.swift | 23 +- .../CameraScreen/Sources/ModeComponent.swift | 6 +- .../ChatMessageGiftBubbleContentNode.swift | 4 +- .../Sources/ComposeTodoScreen.swift | 2 +- .../Sources/NewContactScreen.swift | 122 +- .../Sources/GiftItemComponent.swift | 18 +- .../Sources/GiftOptionsScreen.swift | 15 +- .../Components/Gifts/GiftSetupScreen/BUILD | 2 + .../Sources/ChatGiftPreviewItem.swift | 2 +- .../Sources/GiftSetupScreen.swift | 255 +- .../Sources/RemainingCountComponent.swift | 499 +-- .../Components/Gifts/GiftViewScreen/BUILD | 4 + .../Sources/BadgeLabelView.swift | 166 + .../Sources/GiftAuctionInfoScreen.swift | 646 ++++ .../Sources/GiftAuctionScreen.swift | 2950 +++++++++++++++++ .../Sources/GiftValueScreen.swift | 63 +- .../Sources/GiftViewScreen.swift | 4 +- .../Sources/ReorderingGestureRecognizer.swift | 8 +- .../ListItemSliderSelectorComponent.swift | 10 +- .../Sources/ListTextFieldItemComponent.swift | 23 +- .../MetalResources/EditorVideo.metal | 1 - .../Sources/MediaEditorComposer.swift | 20 +- .../Sources/MediaEditorRenderer.swift | 4 +- .../MediaEditor/Sources/RenderPass.swift | 2 +- .../Sources/MessagePriceItem.swift | 3 +- .../Sources/PeerInfoScreen.swift | 2 +- .../Sources/SearchInputPanelComponent.swift | 8 +- .../Components/ShareWithPeersScreen/BUILD | 6 +- .../Sources/CategoryListItemComponent.swift | 4 +- .../Sources/LiveStreamSettingsScreen.swift | 1421 ++++++++ .../Sources/ShareWithPeersScreen.swift | 19 +- .../Sources/ShareWithPeersScreenState.swift | 5 +- .../Sources/StoryAuthorInfoComponent.swift | 9 +- .../Sources/StoryItemContentComponent.swift | 9 +- .../StoryItemSetContainerComponent.swift | 1 + .../Navigation/Info.imageset/Contents.json | 12 + .../Navigation/Info.imageset/infoheader.pdf | Bin 0 -> 6762 bytes .../Premium/Auction/Contents.json | 9 + .../Auction/Drop.imageset/Contents.json | 12 + .../Premium/Auction/Drop.imageset/drop.pdf | Bin 0 -> 5869 bytes .../Auction/NextDrop.imageset/Contents.json | 12 + .../Auction/NextDrop.imageset/nextdrop.pdf | Bin 0 -> 8583 bytes .../Auction/Refund.imageset/Contents.json | 12 + .../Auction/Refund.imageset/starsrefund.pdf | Bin 0 -> 15493 bytes .../Sources/ChatControllerAdminBanUsers.swift | 4 +- .../Sources/SharedAccountContext.swift | 12 +- .../Sources/TelegramRootController.swift | 2 +- .../Sources/GroupCallContext.swift | 14 +- 103 files changed, 8267 insertions(+), 1762 deletions(-) create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionScreen.swift create mode 100644 submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/infoheader.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/drop.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/NextDrop.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/NextDrop.imageset/nextdrop.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/Refund.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Auction/Refund.imageset/starsrefund.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ff061eca9f..5a906f7fc9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15163,3 +15163,8 @@ Error: %8$@"; "Privacy.SavedMusic.CustomHelp" = "You can restrict who can see your saved music with granular precision."; "Privacy.SavedMusic.AlwaysShareWith.Title" = "Always Share With"; "Privacy.SavedMusic.NeverShareWith.Title" = "Never Share With"; + +"ScheduledMessages.Reminder.Delete" = "Delete Reminder"; +"ScheduledMessages.Reminder.DeleteMany" = "Delete Reminders"; + +"Gift.Setup.NextDropIn" = "next drop in {m}:{s}"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 70e8134209..2e5df8fa0e 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1374,7 +1374,9 @@ public protocol SharedAccountContext: AnyObject { func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController - + func makeGiftAuctionInfoScreen(context: AccountContext, gift: StarGift, completion: (() -> Void)?) -> ViewController + func makeGiftAuctionScreen(context: AccountContext, gift: StarGift, auctionContext: GiftAuctionContext) -> ViewController + func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) @@ -1416,7 +1418,7 @@ public protocol SharedAccountContext: AnyObject { func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) - func makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController + func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index bc480854c5..afdfcd5846 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -73,7 +73,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { var presentationData: PresentationData { didSet { - self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor + self.pillView.tintColor = self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(self.presentationData.theme.overallDarkAppearance ? 0.2 : 0.07) } } @@ -107,9 +107,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { self.container.shouldAnimateDisappearance = true self.pillView = UIImageView() - self.pillView.alpha = 0.2 - self.pillView.image = generateFilledRoundedRectImage(size: CGSize(width: 36.0, height: 5.0), cornerRadius: 2.5, color: .black)?.withRenderingMode(.alwaysTemplate) - self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor + self.pillView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate) + self.pillView.tintColor = self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(self.presentationData.theme.overallDarkAppearance ? 0.2 : 0.07) super.init() @@ -620,10 +619,9 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { transition.updateTransformScale(node: self.container, scale: containerScale) self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition) - if let image = self.pillView.image { - transition.updateFrame(view: self.pillView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((clipFrame.width - image.size.width) / 2.0), y: 5.0), size: image.size)) - self.pillView.isHidden = layout.metrics.isTablet - } + let pillSize = CGSize(width: 36.0, height: 5.0) + transition.updateFrame(view: self.pillView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((clipFrame.width - pillSize.width) / 2.0), y: 5.0), size: pillSize)) + self.pillView.isHidden = layout.metrics.isTablet self.isUpdatingState = false } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 562958ea80..01a4669a07 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -323,7 +323,7 @@ private final class AttachButtonComponent: CombinedComponent { truncationType: .end, maximumNumberOfLines: 1 ), - availableSize: CGSize(width: 60.0, height: context.availableSize.height), + availableSize: CGSize(width: 64.0, height: context.availableSize.height), transition: .immediate ) @@ -1892,7 +1892,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { let textPanelSideInset: CGFloat = 8.0 let defaultPanelSideInset: CGFloat = glassPanelSideInset - let panelSideInset: CGFloat = isSelecting ? textPanelSideInset : defaultPanelSideInset + let panelSideInset: CGFloat = (isSelecting ? textPanelSideInset : defaultPanelSideInset) + layout.safeInsets.left var textPanelHeight: CGFloat = 0.0 var textPanelWidth: CGFloat = 0.0 if let textInputPanelNode = self.textInputPanelNode { diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 2312b8353a..7c599191f3 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -126,7 +126,8 @@ private final class CameraContext { private let audioLevelPipe = ValuePipe() fileprivate let modeChangePromise = ValuePromise(.none) - var videoOutput: CameraVideoOutput? + var mainVideoOutput: CameraVideoOutput? + var additionalVideoOutput: CameraVideoOutput? var simplePreviewView: CameraSimplePreviewView? var secondaryPreviewView: CameraSimplePreviewView? @@ -345,11 +346,21 @@ private final class CameraContext { if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } + + if sampleBuffer.type == kCMMediaType_Video { + Queue.mainQueue().async { + self.mainVideoOutput?.push(sampleBuffer, mirror: front) + } + } + self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastSnapshotTimestamp = timestamp self.savedSnapshot = true } } + self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in + let _ = self + } self.additionalDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in guard let self, let additionalDeviceContext = self.additionalDeviceContext else { return @@ -360,6 +371,11 @@ private final class CameraContext { if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } + + Queue.mainQueue().async { + self.additionalVideoOutput?.push(sampleBuffer, mirror: front) + } + self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastAdditionalSnapshotTimestamp = timestamp self.savedAdditionalSnapshot = true @@ -387,10 +403,8 @@ private final class CameraContext { front = connection.inputPorts.first?.sourceDevicePosition == .front } - if sampleBuffer.type == kCMMediaType_Video { - Queue.mainQueue().async { - self.videoOutput?.push(sampleBuffer, mirror: front) - } + Queue.mainQueue().async { + self.mainVideoOutput?.push(sampleBuffer, mirror: front) } let timestamp = CACurrentMediaTime() @@ -400,48 +414,52 @@ private final class CameraContext { self.savedSnapshot = true } } - if self.initialConfiguration.reportAudioLevel { - self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in - guard let self else { - return - } - var blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) - let numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBuffer) - var audioBufferList = AudioBufferList() - - CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, bufferListSizeNeededOut: nil, bufferListOut: &audioBufferList, bufferListSize: MemoryLayout.size, blockBufferAllocator: nil, blockBufferMemoryAllocator: nil, flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, blockBufferOut: &blockBuffer) - -// for bufferCount in 0.., count: Int) { - for i in 0..= 1200 { - let level = Float(self.micLevelPeak) / 4000.0 - self.audioLevelPipe.putNext(level) - - self.micLevelPeak = 0 - self.micLevelPeakCount = 0 - } - } - } - } + self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in + let _ = self } + +// if self.initialConfiguration.reportAudioLevel { +// self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in +// guard let self else { +// return +// } +// var blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) +// let numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBuffer) +// var audioBufferList = AudioBufferList() +// +// CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, bufferListSizeNeededOut: nil, bufferListOut: &audioBufferList, bufferListSize: MemoryLayout.size, blockBufferAllocator: nil, blockBufferMemoryAllocator: nil, flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, blockBufferOut: &blockBuffer) +// +//// for bufferCount in 0.., count: Int) { +// for i in 0..= 1200 { +// let level = Float(self.micLevelPeak) / 4000.0 +// self.audioLevelPipe.putNext(level) +// +// self.micLevelPeak = 0 +// self.micLevelPeakCount = 0 +// } +// } +// } +// } +// } self.mainDeviceContext?.output.processCodes = { [weak self] codes in self?.detectedCodesPipe.putNext(codes) } @@ -1016,15 +1034,33 @@ public final class Camera { } } - public func setVideoOutput(_ output: CameraVideoOutput?) { + public func setMainVideoOutput(_ output: CameraVideoOutput?) { let outputRef: Unmanaged? = output.flatMap { Unmanaged.passRetained($0) } self.queue.async { if let context = self.contextRef?.takeUnretainedValue() { if let outputRef { - context.videoOutput = outputRef.takeUnretainedValue() + context.mainVideoOutput = outputRef.takeUnretainedValue() outputRef.release() } else { - context.videoOutput = nil + context.mainVideoOutput = nil + } + } else { + Queue.mainQueue().async { + outputRef?.release() + } + } + } + } + + public func setAdditionalVideoOutput(_ output: CameraVideoOutput?) { + let outputRef: Unmanaged? = output.flatMap { Unmanaged.passRetained($0) } + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + if let outputRef { + context.additionalVideoOutput = outputRef.takeUnretainedValue() + outputRef.release() + } else { + context.additionalVideoOutput = nil } } else { Queue.mainQueue().async { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index bc4b50fa0f..3e37e972ff 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -642,8 +642,8 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) - } else { -// self.processAudioBuffer?(sampleBuffer) + } else if sampleBuffer.type == kCMMediaType_Audio { + self.processAudioBuffer?(sampleBuffer) } if let masterOutput = self.masterOutput { diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index 90fdff3d04..c00d40210d 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -280,7 +280,7 @@ final class ComposePollScreenComponent: Component { let theme = environment.theme.withModalBlocksBackground() let backgroundView = UIImageView(image: generateReorderingBackgroundImage(backgroundColor: theme.list.itemBlocksBackgroundColor)) - backgroundView.frame = wrapperView.bounds.insetBy(dx: -10.0, dy: -10.0) + backgroundView.frame = wrapperView.bounds.insetBy(dx: -16.0, dy: -16.0) snapshotView.frame = snapshotView.bounds wrapperView.addSubview(backgroundView) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 2526b7739a..8f6789ff3a 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -740,34 +740,31 @@ public class ContactsController: ViewController { switch status { case .allowed: - //let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { let controller = strongSelf.context.sharedContext.makeNewContactScreen( context: strongSelf.context, - firstName: nil, - lastName: nil, - phoneNumber: nil + peer: nil, + phoneNumber: nil, + completion: { [weak self] peer, stableId, contactData in + guard let strongSelf = self else { + return + } + if let peer { + Queue.mainQueue().async { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { + navigationController.pushViewController(infoController) + } + } + } + } else if let stableId, let contactData { + if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { + navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) + } + } + } ) navigationController.pushViewController(controller) - -// navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in -// guard let strongSelf = self else { -// return -// } -// if let peer = peer { -// DispatchQueue.main.async { -// if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { -// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { -// navigationController.pushViewController(infoController) -// } -// } -// } -// } else { -// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { -// navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) -// } -// } -// }), completed: nil, cancelled: nil)) } case .notDetermined: DeviceAccess.authorizeAccess(to: .contacts) diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 0e64b2afe4..67f534a4cf 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -722,8 +722,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo theme: self.presentationData.theme, strings: self.presentationData.strings, metrics: layout.metrics, - placeholder: nil, - resetText: nil, + safeInsets: layout.safeInsets, updated: { [weak self] query in guard let self else { return @@ -740,11 +739,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo ) ), environment: {}, - containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + containerSize: CGSize(width: layout.size.width, height: layout.size.height) ) let bottomInset: CGFloat = layout.insets(options: .input).bottom - let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + let searchInputFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { if searchInputView.superview == nil { self.view.addSubview(searchInputView) diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index 867dc0b843..017cc15f01 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -340,11 +340,9 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style - //TODO:localize - self.title = "Select Country" - if glass { - + //TODO:localize + self.title = "Select Country" } else { let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in self?.dismissed?() diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift index bedee5acaf..6f9bfbb733 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift @@ -211,7 +211,6 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, private var searchInput: ComponentView? var isSearching = true - private var validLayout: ContainerViewLayout? init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, glass: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) { @@ -300,12 +299,14 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, transition.updateFrame(view: self.tableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) transition.updateFrame(view: self.searchTableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) - let edgeEffectHeight: CGFloat = 88.0 - let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) - transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) - self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + if self.glass { + let edgeEffectHeight: CGFloat = 88.0 + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + } - if self.isSearching { + if self.glass && self.isSearching { let searchInput: ComponentView if let current = self.searchInput { searchInput = current @@ -321,6 +322,7 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, theme: self.theme, strings: self.strings, metrics: layout.metrics, + safeInsets: layout.safeInsets, updated: { [weak self] query in guard let self else { return @@ -337,10 +339,10 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, ) ), environment: {}, - containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + containerSize: CGSize(width: layout.size.width, height: layout.size.height) ) let bottomInset: CGFloat = layout.insets(options: .input).bottom - let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + let searchInputFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) if let searchInputView = searchInput.view as? SearchInputPanelComponent.View { if searchInputView.superview == nil { self.view.addSubview(searchInputView) diff --git a/submodules/Display/Source/NativeWindowHostView.swift b/submodules/Display/Source/NativeWindowHostView.swift index b20ac51f00..1c145d63db 100644 --- a/submodules/Display/Source/NativeWindowHostView.swift +++ b/submodules/Display/Source/NativeWindowHostView.swift @@ -87,7 +87,7 @@ private final class WindowRootViewControllerView: UIView { } } -private final class WindowRootViewController: UIViewController { +private final class WindowRootViewController: UIViewController, UIWindowSceneDelegate { private var voiceOverStatusObserver: AnyObject? private var registeredForPreviewing = false @@ -183,7 +183,7 @@ private final class WindowRootViewController: UIViewController { init() { super.init(nibName: nil, bundle: nil) - + self.extendedLayoutIncludesOpaqueBars = true self.voiceOverStatusObserver = NotificationCenter.default.addObserver(forName: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil, queue: OperationQueue.main, using: { _ in @@ -194,6 +194,10 @@ private final class WindowRootViewController: UIViewController { } else { self._systemUserInterfaceStyle.set(.light) } + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + windowScene.delegate = self + } } required init?(coder aDecoder: NSCoder) { @@ -206,6 +210,11 @@ private final class WindowRootViewController: UIViewController { } } + @available(iOS 26.0, *) + func preferredWindowingControlStyle(for windowScene: UIWindowScene) -> UIWindowScene.WindowingControlStyle { + return .minimal + } + override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { return self.gestureEdges } diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index 4354213cec..a590968359 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -250,7 +250,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega bottomController.viewWillAppear(true) let bottomNode = bottomController.displayNode - let screenCornerRadius = self.minimizedContainer == nil ? layout.deviceMetrics.screenCornerRadius : 0.0 + let screenCornerRadius = self.minimizedContainer == nil && self.state.canBeClosed != true ? layout.deviceMetrics.screenCornerRadius : 0.0 let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: screenCornerRadius, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in if let strongSelf = self { if let top = strongSelf.state.top { @@ -491,7 +491,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega } toValue.value.setIgnoreAppearanceMethodInvocations(false) - let screenCornerRadius = self.minimizedContainer == nil ? layout.deviceMetrics.screenCornerRadius : 0.0 + let screenCornerRadius = self.minimizedContainer == nil && self.state.canBeClosed != true ? layout.deviceMetrics.screenCornerRadius : 0.0 let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: screenCornerRadius, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in guard let strongSelf = self else { return diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 54ff57dc37..593005a7e5 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -241,8 +241,10 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open func navigationLayout(layout: ContainerViewLayout) -> NavigationLayout { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var defaultNavigationBarHeight: CGFloat - if self._presentedInModal && layout.orientation == .portrait { - defaultNavigationBarHeight = self._hasGlassStyle ? 66.0 : 56.0 + if self._presentedInModal && self._hasGlassStyle { + defaultNavigationBarHeight = 66.0 + } else if self._presentedInModal && layout.orientation == .portrait { + defaultNavigationBarHeight = 56.0 } else { defaultNavigationBarHeight = 44.0 } diff --git a/submodules/LegacyComponents/Sources/TGIconSwitchView.m b/submodules/LegacyComponents/Sources/TGIconSwitchView.m index 8a91e77830..c0ade41409 100644 --- a/submodules/LegacyComponents/Sources/TGIconSwitchView.m +++ b/submodules/LegacyComponents/Sources/TGIconSwitchView.m @@ -45,11 +45,17 @@ static const void *positionChangedKey = &positionChangedKey; if (self != nil) { _offIconView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PermissionSwitchOff.png")]; _onIconView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PermissionSwitchOn.png")]; - self.layer.cornerRadius = 17.0f; + if (iosMajorVersion() >= 26) { + self.layer.cornerRadius = 14.0f; + } else { + self.layer.cornerRadius = 17.0f; + } self.backgroundColor = [UIColor redColor]; self.tintColor = [UIColor redColor]; UIView *handleView = self.subviews[0].subviews.lastObject; - if (iosMajorVersion() >= 13) { + if (iosMajorVersion() >= 26) { + handleView = self.subviews[0].subviews.lastObject; + } else if (iosMajorVersion() >= 13) { handleView = self.subviews[0].subviews[1].subviews.lastObject; } else { handleView = self.subviews[0].subviews.lastObject; @@ -91,7 +97,9 @@ static const void *positionChangedKey = &positionChangedKey; - (void)updateIconFrame { CGPoint offset = CGPointZero; - if (iosMajorVersion() >= 12) { + if (iosMajorVersion() >= 26) { + offset = CGPointMake(-10.0, -9.0 - TGScreenPixel); + } else if (iosMajorVersion() >= 12) { offset = CGPointMake(-7.0, -3.0); } diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 863e353447..3d45420937 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -1225,7 +1225,7 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil if let backgroundNode = strongSelf.backgroundNode { - strongSelf.maskNode.frame = backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.maskNode.frame = backgroundNode.frame.insetBy(dx: params.leftInset, dy: -UIScreenPixel) } } diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index f233497ba3..176b4b2f7a 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -1453,8 +1453,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM theme: self.presentationData.theme, strings: self.presentationData.strings, metrics: layout.metrics, + safeInsets: layout.safeInsets, placeholder: self.presentationData.strings.Map_Search, - resetText: nil, updated: { [weak self] query in guard let self, let controller = self.controller else { return @@ -1470,11 +1470,11 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM ) ), environment: {}, - containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + containerSize: CGSize(width: layout.size.width, height: layout.size.height) ) let bottomInset: CGFloat = layout.insets(options: .input).bottom - let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + let searchInputFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) if let searchInputView = searchInput.view as? SearchInputPanelComponent.View { if searchInputView.superview == nil { self.view.addSubview(searchInputView) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 5ebed4a5b6..6f9a8c13cf 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1616,9 +1616,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let firstTime = self.validLayout == nil self.validLayout = (layout, navigationBarHeight) - if firstTime { - self.updateSelectionState(animated: false, updateLayout: false) - } + self.updateSelectionState(animated: false, updateLayout: false) var insets = layout.insets(options: []) insets.top += navigationBarHeight @@ -2592,7 +2590,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att environment: {}, containerSize: barButtonSize ) - let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset, y: barButtonSideInset), size: cancelButtonSize) + let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: cancelButtonSize) if let view = cancelButton.view { if view.superview == nil { self.view.addSubview(view) @@ -2627,7 +2625,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att environment: {}, containerSize: barButtonSize ) - let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset, y: barButtonSideInset), size: moreButtonSize) + let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset - layout.safeInsets.right, y: barButtonSideInset), size: moreButtonSize) if let view = moreButton.view { if view.superview == nil { self.view.addSubview(view) diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift index 577a18f4b2..939d80ae68 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift @@ -292,7 +292,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent transition: context.transition ) - let bottomText = Condition(mode == .create) { + let bottomText = Condition(context.component.mode.isCreate) { bottomText.update( component: MultilineTextComponent( text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StartStreamingInfo, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)), @@ -305,15 +305,15 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent ) } - let buttonAttributedString = NSMutableAttributedString(string: mode == .create ? environment.strings.CreateExternalStream_StartStreaming : environment.strings.Common_Close, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + let buttonAttributedString = NSMutableAttributedString(string: mode.isCreate ? environment.strings.CreateExternalStream_StartStreaming : environment.strings.Common_Close, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( style: .glass, - color: mode == .create ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, - foreground: mode == .create ? .white : theme.list.itemCheckColors.foregroundColor, - pressedColor: mode == .create ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, - isShimmering: mode == .create + color: mode.isCreate ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, + foreground: mode.isCreate ? .white : theme.list.itemCheckColors.foregroundColor, + pressedColor: mode.isCreate ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, + isShimmering: mode.isCreate ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -322,15 +322,20 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent isEnabled: true, displaysProgress: false, action: { [weak state] in - guard let state = state, let controller = controller() else { + guard let state = state, let controller = controller() as? CreateExternalMediaStreamScreen else { return } switch mode { - case .create: - state.createAndJoinGroupCall(baseController: controller, completion: { [weak controller] in - controller?.dismiss() - }) + case let .create(livestream): + if livestream { + controller.completion?() + } else { + state.createAndJoinGroupCall(baseController: controller, completion: { [weak controller] in + controller?.completion?() + controller?.dismiss() + }) + } case .view: controller.dismiss() } @@ -480,7 +485,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: context.availableSize.height - bottomInset - button.size.height), size: button.size) - if let bottomText = bottomText { + if let bottomText { context.add(bottomText .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.minY - 12.0 - bottomText.size.height / 2.0)) ) @@ -496,19 +501,35 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent } public final class CreateExternalMediaStreamScreen: ViewControllerComponentContainer { - public enum Mode { - case create + public enum Mode: Equatable { + case create(liveStream: Bool) case view + + var isCreate: Bool { + if case .create = self { + return true + } else { + return false + } + } } private let context: AccountContext private let peerId: EnginePeer.Id private let mode: Mode + fileprivate let completion: (() -> Void)? - public init(context: AccountContext, peerId: EnginePeer.Id, credentialsPromise: Promise?, mode: Mode) { + public init( + context: AccountContext, + peerId: EnginePeer.Id, + credentialsPromise: Promise?, + mode: Mode, + completion: (() -> Void)? = nil + ) { self.context = context self.peerId = peerId self.mode = mode + self.completion = completion super.init(context: context, component: CreateExternalMediaStreamScreenComponent(context: context, peerId: peerId, mode: mode, credentialsPromise: credentialsPromise), navigationBarAppearance: .none, theme: .dark) diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index 3da170adfe..ea1f40bda9 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -50,7 +50,7 @@ private func getUserPeer(engine: TelegramEngine, peerId: EnginePeer.Id) -> Signa public func openAddPersonContactImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void, completion: @escaping () -> Void = {}) { let _ = (getUserPeer(engine: context.engine, peerId: peerId) |> deliverOnMainQueue).start(next: { peer, statusSettings in - guard let peer, case let .user(user) = peer, let contactData = DeviceContactExtendedData(peer: peer) else { + guard let peer, case let .user(user) = peer else { return } @@ -59,13 +59,30 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio shareViaException = statusSettings.contains(.addExceptionWhenAddingContact) } - pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in - if let peer = peer as? TelegramUser { - completion() - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.AddContact_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), nil) + + let _ = shareViaException + let controller = context.sharedContext.makeNewContactScreen( + context: context, + peer: peer, + phoneNumber: user.phone, + completion: { peer, _, _ in + if let peer { + completion() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.AddContact_StatusSuccess(peer.compactDisplayTitle).string, true)), nil) + } } - }), completed: nil, cancelled: nil)) + ) + pushController(controller) + +// pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in +// if let peer = peer as? TelegramUser { +// completion() +// +// let presentationData = context.sharedContext.currentPresentationData.with { $0 } +// present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.AddContact_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), nil) +// } +// }), completed: nil, cancelled: nil)) }) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 66a8c7f560..f4d3ecc2bc 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -66,6 +66,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[84480319] = { return Api.AttachMenuPeerType.parse_attachMenuPeerTypeChat($0) } dict[-247016673] = { return Api.AttachMenuPeerType.parse_attachMenuPeerTypePM($0) } dict[2104224014] = { return Api.AttachMenuPeerType.parse_attachMenuPeerTypeSameBotPM($0) } + dict[822231244] = { return Api.AuctionBidLevel.parse_auctionBidLevel($0) } dict[-1392388579] = { return Api.Authorization.parse_authorization($0) } dict[-1163561432] = { return Api.AutoDownloadSettings.parse_autoDownloadSettings($0) } dict[-2124403385] = { return Api.AutoSaveException.parse_autoSaveException($0) } @@ -301,7 +302,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) } dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } - dict[-674602536] = { return Api.GroupCall.parse_groupCall($0) } + dict[-273500649] = { return Api.GroupCall.parse_groupCall($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[-297595771] = { return Api.GroupCallDonor.parse_groupCallDonor($0) } dict[445316222] = { return Api.GroupCallMessage.parse_groupCallMessage($0) } @@ -396,6 +397,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-625298705] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftStars($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } + dict[2010287526] = { return Api.InputInvoice.parse_inputInvoiceStarGiftAuctionBid($0) } dict[153344209] = { return Api.InputInvoice.parse_inputInvoiceStarGiftDropOriginalDetails($0) } dict[-1710536520] = { return Api.InputInvoice.parse_inputInvoiceStarGiftPrepaidUpgrade($0) } dict[-1012968668] = { return Api.InputInvoice.parse_inputInvoiceStarGiftResale($0) } @@ -588,7 +590,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) } dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } dict[1456486804] = { return Api.MessageAction.parse_messageActionGiftCode($0) } - dict[1818391802] = { return Api.MessageAction.parse_messageActionGiftPremium($0) } + dict[1223234306] = { return Api.MessageAction.parse_messageActionGiftPremium($0) } dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) } dict[-1465661799] = { return Api.MessageAction.parse_messageActionGiftTon($0) } dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) } @@ -980,6 +982,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[520210263] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdBackdrop($0) } dict[1219145276] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdModel($0) } dict[1242965043] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdPattern($0) } + dict[-483580782] = { return Api.StarGiftAuctionState.parse_starGiftAuctionState($0) } + dict[676935593] = { return Api.StarGiftAuctionState.parse_starGiftAuctionStateFinished($0) } + dict[-30197422] = { return Api.StarGiftAuctionState.parse_starGiftAuctionStateNotModified($0) } + dict[-165829476] = { return Api.StarGiftAuctionUserState.parse_starGiftAuctionUserState($0) } dict[-1653926992] = { return Api.StarGiftCollection.parse_starGiftCollection($0) } dict[-1712704739] = { return Api.StarGiftUpgradePrice.parse_starGiftUpgradePrice($0) } dict[-586389774] = { return Api.StarRefProgram.parse_starRefProgram($0) } @@ -1182,6 +1188,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) } dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) } dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) } + dict[1222788802] = { return Api.Update.parse_updateStarGiftAuctionState($0) } + dict[-598150370] = { return Api.Update.parse_updateStarGiftAuctionUserState($0) } dict[1317053305] = { return Api.Update.parse_updateStarsBalance($0) } dict[-1518030823] = { return Api.Update.parse_updateStarsRevenueStatus($0) } dict[834816008] = { return Api.Update.parse_updateStickerSets($0) } @@ -1471,6 +1479,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1803939105] = { return Api.payments.ResaleStarGifts.parse_resaleStarGifts($0) } dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) } dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } + dict[-2061303084] = { return Api.payments.StarGiftAuctionState.parse_starGiftAuctionState($0) } dict[-1977011469] = { return Api.payments.StarGiftCollections.parse_starGiftCollections($0) } dict[-1598402793] = { return Api.payments.StarGiftCollections.parse_starGiftCollectionsNotModified($0) } dict[1038213101] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) } @@ -1619,6 +1628,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.AttachMenuPeerType: _1.serialize(buffer, boxed) + case let _1 as Api.AuctionBidLevel: + _1.serialize(buffer, boxed) case let _1 as Api.Authorization: _1.serialize(buffer, boxed) case let _1 as Api.AutoDownloadSettings: @@ -2203,6 +2214,10 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.StarGiftAttributeId: _1.serialize(buffer, boxed) + case let _1 as Api.StarGiftAuctionState: + _1.serialize(buffer, boxed) + case let _1 as Api.StarGiftAuctionUserState: + _1.serialize(buffer, boxed) case let _1 as Api.StarGiftCollection: _1.serialize(buffer, boxed) case let _1 as Api.StarGiftUpgradePrice: @@ -2619,6 +2634,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.payments.SavedStarGifts: _1.serialize(buffer, boxed) + case let _1 as Api.payments.StarGiftAuctionState: + _1.serialize(buffer, boxed) case let _1 as Api.payments.StarGiftCollections: _1.serialize(buffer, boxed) case let _1 as Api.payments.StarGiftUpgradePreview: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index f6e3fd0d8a..2177c01e8c 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -386,6 +386,50 @@ public extension Api { } } +public extension Api { + enum AuctionBidLevel: TypeConstructorDescription { + case auctionBidLevel(pos: Int32, amount: Int64, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .auctionBidLevel(let pos, let amount, let date): + if boxed { + buffer.appendInt32(822231244) + } + serializeInt32(pos, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .auctionBidLevel(let pos, let amount, let date): + return ("auctionBidLevel", [("pos", pos as Any), ("amount", amount as Any), ("date", date as Any)]) + } + } + + public static func parse_auctionBidLevel(_ reader: BufferReader) -> AuctionBidLevel? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.AuctionBidLevel.auctionBidLevel(pos: _1!, amount: _2!, date: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum Authorization: TypeConstructorDescription { case authorization(flags: Int32, hash: Int64, deviceModel: String, platform: String, systemVersion: String, apiId: Int32, appName: String, appVersion: String, dateCreated: Int32, dateActive: Int32, ip: String, country: String, region: String) @@ -1168,61 +1212,3 @@ public extension Api { } } -public extension Api { - enum BotBusinessConnection: TypeConstructorDescription { - case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32, rights: Api.BusinessBotRights?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights): - if boxed { - buffer.appendInt32(-1892371723) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(connectionId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {rights!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights): - return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any), ("rights", rights as Any)]) - } - } - - public static func parse_botBusinessConnection(_ reader: BufferReader) -> BotBusinessConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Api.BusinessBotRights? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index eab67ad0ff..ea7547bf02 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -566,6 +566,7 @@ public extension Api { case inputInvoicePremiumGiftStars(flags: Int32, userId: Api.InputUser, months: Int32, message: Api.TextWithEntities?) case inputInvoiceSlug(slug: String) case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?) + case inputInvoiceStarGiftAuctionBid(flags: Int32, peer: Api.InputPeer, giftId: Int64, bidAmount: Int64, message: Api.TextWithEntities?) case inputInvoiceStarGiftDropOriginalDetails(stargift: Api.InputSavedStarGift) case inputInvoiceStarGiftPrepaidUpgrade(peer: Api.InputPeer, hash: String) case inputInvoiceStarGiftResale(flags: Int32, slug: String, toId: Api.InputPeer) @@ -632,6 +633,16 @@ public extension Api { serializeInt64(giftId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} break + case .inputInvoiceStarGiftAuctionBid(let flags, let peer, let giftId, let bidAmount, let message): + if boxed { + buffer.appendInt32(2010287526) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(giftId, buffer: buffer, boxed: false) + serializeInt64(bidAmount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} + break case .inputInvoiceStarGiftDropOriginalDetails(let stargift): if boxed { buffer.appendInt32(153344209) @@ -694,6 +705,8 @@ public extension Api { return ("inputInvoiceSlug", [("slug", slug as Any)]) case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): return ("inputInvoiceStarGift", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("message", message as Any)]) + case .inputInvoiceStarGiftAuctionBid(let flags, let peer, let giftId, let bidAmount, let message): + return ("inputInvoiceStarGiftAuctionBid", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("bidAmount", bidAmount as Any), ("message", message as Any)]) case .inputInvoiceStarGiftDropOriginalDetails(let stargift): return ("inputInvoiceStarGiftDropOriginalDetails", [("stargift", stargift as Any)]) case .inputInvoiceStarGiftPrepaidUpgrade(let peer, let hash): @@ -842,6 +855,33 @@ public extension Api { return nil } } + public static func parse_inputInvoiceStarGiftAuctionBid(_ reader: BufferReader) -> InputInvoice? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: Api.TextWithEntities? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputInvoice.inputInvoiceStarGiftAuctionBid(flags: _1!, peer: _2!, giftId: _3!, bidAmount: _4!, message: _5) + } + else { + return nil + } + } public static func parse_inputInvoiceStarGiftDropOriginalDetails(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputSavedStarGift? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index d79ae1726d..53200a29ce 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -1044,7 +1044,7 @@ public extension Api { case messageActionGameScore(gameId: Int64, score: Int32) case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) - case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) + case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, days: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) case messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case messageActionGiftTon(flags: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, transactionId: String?) case messageActionGiveawayLaunch(flags: Int32, stars: Int64?) @@ -1235,14 +1235,14 @@ public extension Api { if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {message!.serialize(buffer, true)} break - case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message): + case .messageActionGiftPremium(let flags, let currency, let amount, let days, let cryptoCurrency, let cryptoAmount, let message): if boxed { - buffer.appendInt32(1818391802) + buffer.appendInt32(1223234306) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(currency, buffer: buffer, boxed: false) serializeInt64(amount, buffer: buffer, boxed: false) - serializeInt32(months, buffer: buffer, boxed: false) + serializeInt32(days, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} @@ -1627,8 +1627,8 @@ public extension Api { return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)]) case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount, let message): return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)]) - case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message): - return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)]) + case .messageActionGiftPremium(let flags, let currency, let amount, let days, let cryptoCurrency, let cryptoAmount, let message): + return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("days", days as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)]) case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId): return ("messageActionGiftStars", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("stars", stars as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("transactionId", transactionId as Any)]) case .messageActionGiftTon(let flags, let currency, let amount, let cryptoCurrency, let cryptoAmount, let transactionId): @@ -1991,7 +1991,7 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7) + return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index ddea45fec8..2c7da602bb 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -1,3 +1,61 @@ +public extension Api { + enum BotBusinessConnection: TypeConstructorDescription { + case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32, rights: Api.BusinessBotRights?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights): + if boxed { + buffer.appendInt32(-1892371723) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(connectionId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {rights!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights): + return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any), ("rights", rights as Any)]) + } + } + + public static func parse_botBusinessConnection(_ reader: BufferReader) -> BotBusinessConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Api.BusinessBotRights? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6) + } + else { + return nil + } + } + + } +} public extension Api { enum BotCommand: TypeConstructorDescription { case botCommand(command: String, description: String) @@ -1184,49 +1242,3 @@ public extension Api { } } -public extension Api { - enum BusinessGreetingMessage: TypeConstructorDescription { - case businessGreetingMessage(shortcutId: Int32, recipients: Api.BusinessRecipients, noActivityDays: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays): - if boxed { - buffer.appendInt32(-451302485) - } - serializeInt32(shortcutId, buffer: buffer, boxed: false) - recipients.serialize(buffer, true) - serializeInt32(noActivityDays, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays): - return ("businessGreetingMessage", [("shortcutId", shortcutId as Any), ("recipients", recipients as Any), ("noActivityDays", noActivityDays as Any)]) - } - } - - public static func parse_businessGreetingMessage(_ reader: BufferReader) -> BusinessGreetingMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.BusinessRecipients? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index f4b90533b2..c7c6da55ad 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -788,6 +788,158 @@ public extension Api { } } +public extension Api { + enum StarGiftAuctionState: TypeConstructorDescription { + case starGiftAuctionState(version: Int32, minBidAmount: Int64, bidLevels: [Api.AuctionBidLevel], topBidders: [Int64], dropSize: Int32, nextDropAt: Int32, dropsLeft: Int32, dropsTotal: Int32) + case starGiftAuctionStateFinished + case starGiftAuctionStateNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionState(let version, let minBidAmount, let bidLevels, let topBidders, let dropSize, let nextDropAt, let dropsLeft, let dropsTotal): + if boxed { + buffer.appendInt32(-483580782) + } + serializeInt32(version, buffer: buffer, boxed: false) + serializeInt64(minBidAmount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(bidLevels.count)) + for item in bidLevels { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topBidders.count)) + for item in topBidders { + serializeInt64(item, buffer: buffer, boxed: false) + } + serializeInt32(dropSize, buffer: buffer, boxed: false) + serializeInt32(nextDropAt, buffer: buffer, boxed: false) + serializeInt32(dropsLeft, buffer: buffer, boxed: false) + serializeInt32(dropsTotal, buffer: buffer, boxed: false) + break + case .starGiftAuctionStateFinished: + if boxed { + buffer.appendInt32(676935593) + } + + break + case .starGiftAuctionStateNotModified: + if boxed { + buffer.appendInt32(-30197422) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGiftAuctionState(let version, let minBidAmount, let bidLevels, let topBidders, let dropSize, let nextDropAt, let dropsLeft, let dropsTotal): + return ("starGiftAuctionState", [("version", version as Any), ("minBidAmount", minBidAmount as Any), ("bidLevels", bidLevels as Any), ("topBidders", topBidders as Any), ("dropSize", dropSize as Any), ("nextDropAt", nextDropAt as Any), ("dropsLeft", dropsLeft as Any), ("dropsTotal", dropsTotal as Any)]) + case .starGiftAuctionStateFinished: + return ("starGiftAuctionStateFinished", []) + case .starGiftAuctionStateNotModified: + return ("starGiftAuctionStateNotModified", []) + } + } + + public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: [Api.AuctionBidLevel]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AuctionBidLevel.self) + } + var _4: [Int64]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.StarGiftAuctionState.starGiftAuctionState(version: _1!, minBidAmount: _2!, bidLevels: _3!, topBidders: _4!, dropSize: _5!, nextDropAt: _6!, dropsLeft: _7!, dropsTotal: _8!) + } + else { + return nil + } + } + public static func parse_starGiftAuctionStateFinished(_ reader: BufferReader) -> StarGiftAuctionState? { + return Api.StarGiftAuctionState.starGiftAuctionStateFinished + } + public static func parse_starGiftAuctionStateNotModified(_ reader: BufferReader) -> StarGiftAuctionState? { + return Api.StarGiftAuctionState.starGiftAuctionStateNotModified + } + + } +} +public extension Api { + enum StarGiftAuctionUserState: TypeConstructorDescription { + case starGiftAuctionUserState(flags: Int32, bidAmount: Int64?, bidDate: Int32?, minBidAmount: Int64?, acquiredCount: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionUserState(let flags, let bidAmount, let bidDate, let minBidAmount, let acquiredCount): + if boxed { + buffer.appendInt32(-165829476) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(bidAmount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(bidDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(minBidAmount!, buffer: buffer, boxed: false)} + serializeInt32(acquiredCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGiftAuctionUserState(let flags, let bidAmount, let bidDate, let minBidAmount, let acquiredCount): + return ("starGiftAuctionUserState", [("flags", flags as Any), ("bidAmount", bidAmount as Any), ("bidDate", bidDate as Any), ("minBidAmount", minBidAmount as Any), ("acquiredCount", acquiredCount as Any)]) + } + } + + public static func parse_starGiftAuctionUserState(_ reader: BufferReader) -> StarGiftAuctionUserState? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarGiftAuctionUserState.starGiftAuctionUserState(flags: _1!, bidAmount: _2, bidDate: _3, minBidAmount: _4, acquiredCount: _5!) + } + else { + return nil + } + } + + } +} public extension Api { enum StarGiftCollection: TypeConstructorDescription { case starGiftCollection(flags: Int32, collectionId: Int32, title: String, icon: Api.Document?, giftsCount: Int32, hash: Int64) @@ -1442,271 +1594,3 @@ public extension Api { } } -public extension Api { - enum StarsTransaction: TypeConstructorDescription { - case starsTransaction(flags: Int32, id: String, amount: Api.StarsAmount, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?, starrefCommissionPermille: Int32?, starrefPeer: Api.Peer?, starrefAmount: Api.StarsAmount?, paidMessages: Int32?, premiumGiftMonths: Int32?, adsProceedsFromDate: Int32?, adsProceedsToDate: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTransaction(let flags, let id, let amount, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount, let paidMessages, let premiumGiftMonths, let adsProceedsFromDate, let adsProceedsToDate): - if boxed { - buffer.appendInt32(325426864) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(id, buffer: buffer, boxed: false) - amount.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {serializeInt32(transactionDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(transactionUrl!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {serializeBytes(botPayload!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(extendedMedia!.count)) - for item in extendedMedia! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)} - if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 16) != 0 {serializeInt32(starrefCommissionPermille!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {starrefPeer!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {starrefAmount!.serialize(buffer, true)} - if Int(flags) & Int(1 << 19) != 0 {serializeInt32(paidMessages!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 20) != 0 {serializeInt32(premiumGiftMonths!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {serializeInt32(adsProceedsFromDate!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {serializeInt32(adsProceedsToDate!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .starsTransaction(let flags, let id, let amount, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount, let paidMessages, let premiumGiftMonths, let adsProceedsFromDate, let adsProceedsToDate): - return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("amount", amount as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any), ("starrefCommissionPermille", starrefCommissionPermille as Any), ("starrefPeer", starrefPeer as Any), ("starrefAmount", starrefAmount as Any), ("paidMessages", paidMessages as Any), ("premiumGiftMonths", premiumGiftMonths as Any), ("adsProceedsFromDate", adsProceedsFromDate as Any), ("adsProceedsToDate", adsProceedsToDate as Any)]) - } - } - - public static func parse_starsTransaction(_ reader: BufferReader) -> StarsTransaction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.StarsAmount? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.StarsTransactionPeer? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } - var _8: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _9: Int32? - if Int(_1!) & Int(1 << 5) != 0 {_9 = reader.readInt32() } - var _10: String? - if Int(_1!) & Int(1 << 5) != 0 {_10 = parseString(reader) } - var _11: Buffer? - if Int(_1!) & Int(1 << 7) != 0 {_11 = parseBytes(reader) } - var _12: Int32? - if Int(_1!) & Int(1 << 8) != 0 {_12 = reader.readInt32() } - var _13: [Api.MessageMedia]? - if Int(_1!) & Int(1 << 9) != 0 {if let _ = reader.readInt32() { - _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self) - } } - var _14: Int32? - if Int(_1!) & Int(1 << 12) != 0 {_14 = reader.readInt32() } - var _15: Int32? - if Int(_1!) & Int(1 << 13) != 0 {_15 = reader.readInt32() } - var _16: Api.StarGift? - if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.StarGift - } } - var _17: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } - var _18: Int32? - if Int(_1!) & Int(1 << 16) != 0 {_18 = reader.readInt32() } - var _19: Api.Peer? - if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _20: Api.StarsAmount? - if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } } - var _21: Int32? - if Int(_1!) & Int(1 << 19) != 0 {_21 = reader.readInt32() } - var _22: Int32? - if Int(_1!) & Int(1 << 20) != 0 {_22 = reader.readInt32() } - var _23: Int32? - if Int(_1!) & Int(1 << 23) != 0 {_23 = reader.readInt32() } - var _24: Int32? - if Int(_1!) & Int(1 << 23) != 0 {_24 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 19) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 20) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 23) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 23) == 0) || _24 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { - return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StarsTransactionPeer: TypeConstructorDescription { - case starsTransactionPeer(peer: Api.Peer) - case starsTransactionPeerAPI - case starsTransactionPeerAds - case starsTransactionPeerAppStore - case starsTransactionPeerFragment - case starsTransactionPeerPlayMarket - case starsTransactionPeerPremiumBot - case starsTransactionPeerUnsupported - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTransactionPeer(let peer): - if boxed { - buffer.appendInt32(-670195363) - } - peer.serialize(buffer, true) - break - case .starsTransactionPeerAPI: - if boxed { - buffer.appendInt32(-110658899) - } - - break - case .starsTransactionPeerAds: - if boxed { - buffer.appendInt32(1617438738) - } - - break - case .starsTransactionPeerAppStore: - if boxed { - buffer.appendInt32(-1269320843) - } - - break - case .starsTransactionPeerFragment: - if boxed { - buffer.appendInt32(-382740222) - } - - break - case .starsTransactionPeerPlayMarket: - if boxed { - buffer.appendInt32(2069236235) - } - - break - case .starsTransactionPeerPremiumBot: - if boxed { - buffer.appendInt32(621656824) - } - - break - case .starsTransactionPeerUnsupported: - if boxed { - buffer.appendInt32(-1779253276) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .starsTransactionPeer(let peer): - return ("starsTransactionPeer", [("peer", peer as Any)]) - case .starsTransactionPeerAPI: - return ("starsTransactionPeerAPI", []) - case .starsTransactionPeerAds: - return ("starsTransactionPeerAds", []) - case .starsTransactionPeerAppStore: - return ("starsTransactionPeerAppStore", []) - case .starsTransactionPeerFragment: - return ("starsTransactionPeerFragment", []) - case .starsTransactionPeerPlayMarket: - return ("starsTransactionPeerPlayMarket", []) - case .starsTransactionPeerPremiumBot: - return ("starsTransactionPeerPremiumBot", []) - case .starsTransactionPeerUnsupported: - return ("starsTransactionPeerUnsupported", []) - } - } - - public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - if _c1 { - return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) - } - else { - return nil - } - } - public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAPI - } - public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAds - } - public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAppStore - } - public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerFragment - } - public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket - } - public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot - } - public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerUnsupported - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index e0e3dbd81b..20bf032043 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1,3 +1,271 @@ +public extension Api { + enum StarsTransaction: TypeConstructorDescription { + case starsTransaction(flags: Int32, id: String, amount: Api.StarsAmount, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?, starrefCommissionPermille: Int32?, starrefPeer: Api.Peer?, starrefAmount: Api.StarsAmount?, paidMessages: Int32?, premiumGiftMonths: Int32?, adsProceedsFromDate: Int32?, adsProceedsToDate: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTransaction(let flags, let id, let amount, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount, let paidMessages, let premiumGiftMonths, let adsProceedsFromDate, let adsProceedsToDate): + if boxed { + buffer.appendInt32(325426864) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + amount.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(transactionDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(transactionUrl!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {serializeBytes(botPayload!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 9) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(extendedMedia!.count)) + for item in extendedMedia! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 16) != 0 {serializeInt32(starrefCommissionPermille!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {starrefPeer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 17) != 0 {starrefAmount!.serialize(buffer, true)} + if Int(flags) & Int(1 << 19) != 0 {serializeInt32(paidMessages!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 20) != 0 {serializeInt32(premiumGiftMonths!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {serializeInt32(adsProceedsFromDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {serializeInt32(adsProceedsToDate!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starsTransaction(let flags, let id, let amount, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount, let paidMessages, let premiumGiftMonths, let adsProceedsFromDate, let adsProceedsToDate): + return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("amount", amount as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any), ("starrefCommissionPermille", starrefCommissionPermille as Any), ("starrefPeer", starrefPeer as Any), ("starrefAmount", starrefAmount as Any), ("paidMessages", paidMessages as Any), ("premiumGiftMonths", premiumGiftMonths as Any), ("adsProceedsFromDate", adsProceedsFromDate as Any), ("adsProceedsToDate", adsProceedsToDate as Any)]) + } + } + + public static func parse_starsTransaction(_ reader: BufferReader) -> StarsTransaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.StarsTransactionPeer? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer + } + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _9: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_9 = reader.readInt32() } + var _10: String? + if Int(_1!) & Int(1 << 5) != 0 {_10 = parseString(reader) } + var _11: Buffer? + if Int(_1!) & Int(1 << 7) != 0 {_11 = parseBytes(reader) } + var _12: Int32? + if Int(_1!) & Int(1 << 8) != 0 {_12 = reader.readInt32() } + var _13: [Api.MessageMedia]? + if Int(_1!) & Int(1 << 9) != 0 {if let _ = reader.readInt32() { + _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self) + } } + var _14: Int32? + if Int(_1!) & Int(1 << 12) != 0 {_14 = reader.readInt32() } + var _15: Int32? + if Int(_1!) & Int(1 << 13) != 0 {_15 = reader.readInt32() } + var _16: Api.StarGift? + if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.StarGift + } } + var _17: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } + var _18: Int32? + if Int(_1!) & Int(1 << 16) != 0 {_18 = reader.readInt32() } + var _19: Api.Peer? + if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _20: Api.StarsAmount? + if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } } + var _21: Int32? + if Int(_1!) & Int(1 << 19) != 0 {_21 = reader.readInt32() } + var _22: Int32? + if Int(_1!) & Int(1 << 20) != 0 {_22 = reader.readInt32() } + var _23: Int32? + if Int(_1!) & Int(1 << 23) != 0 {_23 = reader.readInt32() } + var _24: Int32? + if Int(_1!) & Int(1 << 23) != 0 {_24 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 19) == 0) || _21 != nil + let _c22 = (Int(_1!) & Int(1 << 20) == 0) || _22 != nil + let _c23 = (Int(_1!) & Int(1 << 23) == 0) || _23 != nil + let _c24 = (Int(_1!) & Int(1 << 23) == 0) || _24 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { + return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StarsTransactionPeer: TypeConstructorDescription { + case starsTransactionPeer(peer: Api.Peer) + case starsTransactionPeerAPI + case starsTransactionPeerAds + case starsTransactionPeerAppStore + case starsTransactionPeerFragment + case starsTransactionPeerPlayMarket + case starsTransactionPeerPremiumBot + case starsTransactionPeerUnsupported + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTransactionPeer(let peer): + if boxed { + buffer.appendInt32(-670195363) + } + peer.serialize(buffer, true) + break + case .starsTransactionPeerAPI: + if boxed { + buffer.appendInt32(-110658899) + } + + break + case .starsTransactionPeerAds: + if boxed { + buffer.appendInt32(1617438738) + } + + break + case .starsTransactionPeerAppStore: + if boxed { + buffer.appendInt32(-1269320843) + } + + break + case .starsTransactionPeerFragment: + if boxed { + buffer.appendInt32(-382740222) + } + + break + case .starsTransactionPeerPlayMarket: + if boxed { + buffer.appendInt32(2069236235) + } + + break + case .starsTransactionPeerPremiumBot: + if boxed { + buffer.appendInt32(621656824) + } + + break + case .starsTransactionPeerUnsupported: + if boxed { + buffer.appendInt32(-1779253276) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starsTransactionPeer(let peer): + return ("starsTransactionPeer", [("peer", peer as Any)]) + case .starsTransactionPeerAPI: + return ("starsTransactionPeerAPI", []) + case .starsTransactionPeerAds: + return ("starsTransactionPeerAds", []) + case .starsTransactionPeerAppStore: + return ("starsTransactionPeerAppStore", []) + case .starsTransactionPeerFragment: + return ("starsTransactionPeerFragment", []) + case .starsTransactionPeerPlayMarket: + return ("starsTransactionPeerPlayMarket", []) + case .starsTransactionPeerPremiumBot: + return ("starsTransactionPeerPremiumBot", []) + case .starsTransactionPeerUnsupported: + return ("starsTransactionPeerUnsupported", []) + } + } + + public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + if _c1 { + return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) + } + else { + return nil + } + } + public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAPI + } + public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAds + } + public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAppStore + } + public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerFragment + } + public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket + } + public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot + } + public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerUnsupported + } + + } +} public extension Api { enum StatsAbsValueAndPrev: TypeConstructorDescription { case statsAbsValueAndPrev(current: Double, previous: Double) @@ -1024,369 +1292,3 @@ public extension Api { } } -public extension Api { - indirect enum StoryReaction: TypeConstructorDescription { - case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) - case storyReactionPublicForward(message: Api.Message) - case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyReaction(let peerId, let date, let reaction): - if boxed { - buffer.appendInt32(1620104917) - } - peerId.serialize(buffer, true) - serializeInt32(date, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - break - case .storyReactionPublicForward(let message): - if boxed { - buffer.appendInt32(-1146411453) - } - message.serialize(buffer, true) - break - case .storyReactionPublicRepost(let peerId, let story): - if boxed { - buffer.appendInt32(-808644845) - } - peerId.serialize(buffer, true) - story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyReaction(let peerId, let date, let reaction): - return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - case .storyReactionPublicForward(let message): - return ("storyReactionPublicForward", [("message", message as Any)]) - case .storyReactionPublicRepost(let peerId, let story): - return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)]) - } - } - - public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) - } - else { - return nil - } - } - public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.StoryReaction.storyReactionPublicForward(message: _1!) - } - else { - return nil - } - } - public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.StoryItem? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - indirect enum StoryView: TypeConstructorDescription { - case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) - case storyViewPublicForward(flags: Int32, message: Api.Message) - case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyView(let flags, let userId, let date, let reaction): - if boxed { - buffer.appendInt32(-1329730875) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} - break - case .storyViewPublicForward(let flags, let message): - if boxed { - buffer.appendInt32(-1870436597) - } - serializeInt32(flags, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - case .storyViewPublicRepost(let flags, let peerId, let story): - if boxed { - buffer.appendInt32(-1116418231) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peerId.serialize(buffer, true) - story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyView(let flags, let userId, let date, let reaction): - return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - case .storyViewPublicForward(let flags, let message): - return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) - case .storyViewPublicRepost(let flags, let peerId, let story): - return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) - } - } - - public static func parse_storyView(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Reaction? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) - } - else { - return nil - } - } - public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Message? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) - } - else { - return nil - } - } - public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.StoryItem? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StoryViews: TypeConstructorDescription { - case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): - if boxed { - buffer.appendInt32(-1923523370) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(viewsCount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions!.count)) - for item in reactions! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentViewers!.count)) - for item in recentViewers! { - serializeInt64(item, buffer: buffer, boxed: false) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): - return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) - } - } - - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: [Api.ReactionCount]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: [Int64]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) - } - else { - return nil - } - } - - } -} -public extension Api { - enum SuggestedPost: TypeConstructorDescription { - case suggestedPost(flags: Int32, price: Api.StarsAmount?, scheduleDate: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .suggestedPost(let flags, let price, let scheduleDate): - if boxed { - buffer.appendInt32(244201445) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {price!.serialize(buffer, true)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .suggestedPost(let flags, let price, let scheduleDate): - return ("suggestedPost", [("flags", flags as Any), ("price", price as Any), ("scheduleDate", scheduleDate as Any)]) - } - } - - public static func parse_suggestedPost(_ reader: BufferReader) -> SuggestedPost? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarsAmount? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.SuggestedPost.suggestedPost(flags: _1!, price: _2, scheduleDate: _3) - } - else { - return nil - } - } - - } -} -public extension Api { - enum TextWithEntities: TypeConstructorDescription { - case textWithEntities(text: String, entities: [Api.MessageEntity]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .textWithEntities(let text, let entities): - if boxed { - buffer.appendInt32(1964978502) - } - serializeString(text, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .textWithEntities(let text, let entities): - return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) - } - } - - public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 4c93a987d1..9327244759 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,3 +1,369 @@ +public extension Api { + indirect enum StoryReaction: TypeConstructorDescription { + case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) + case storyReactionPublicForward(message: Api.Message) + case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyReaction(let peerId, let date, let reaction): + if boxed { + buffer.appendInt32(1620104917) + } + peerId.serialize(buffer, true) + serializeInt32(date, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + break + case .storyReactionPublicForward(let message): + if boxed { + buffer.appendInt32(-1146411453) + } + message.serialize(buffer, true) + break + case .storyReactionPublicRepost(let peerId, let story): + if boxed { + buffer.appendInt32(-808644845) + } + peerId.serialize(buffer, true) + story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyReaction(let peerId, let date, let reaction): + return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + case .storyReactionPublicForward(let message): + return ("storyReactionPublicForward", [("message", message as Any)]) + case .storyReactionPublicRepost(let peerId, let story): + return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)]) + } + } + + public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) + } + else { + return nil + } + } + public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.StoryReaction.storyReactionPublicForward(message: _1!) + } + else { + return nil + } + } + public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.StoryItem? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + indirect enum StoryView: TypeConstructorDescription { + case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) + case storyViewPublicForward(flags: Int32, message: Api.Message) + case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + if boxed { + buffer.appendInt32(-1329730875) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} + break + case .storyViewPublicForward(let flags, let message): + if boxed { + buffer.appendInt32(-1870436597) + } + serializeInt32(flags, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + case .storyViewPublicRepost(let flags, let peerId, let story): + if boxed { + buffer.appendInt32(-1116418231) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + case .storyViewPublicForward(let flags, let message): + return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) + case .storyViewPublicRepost(let flags, let peerId, let story): + return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) + } + } + + public static func parse_storyView(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Reaction? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) + } + else { + return nil + } + } + public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Message? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) + } + else { + return nil + } + } + public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.StoryItem? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StoryViews: TypeConstructorDescription { + case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + if boxed { + buffer.appendInt32(-1923523370) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(viewsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions!.count)) + for item in reactions! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentViewers!.count)) + for item in recentViewers! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _4: [Api.ReactionCount]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } } + var _5: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: [Int64]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) + } + else { + return nil + } + } + + } +} +public extension Api { + enum SuggestedPost: TypeConstructorDescription { + case suggestedPost(flags: Int32, price: Api.StarsAmount?, scheduleDate: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedPost(let flags, let price, let scheduleDate): + if boxed { + buffer.appendInt32(244201445) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {price!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .suggestedPost(let flags, let price, let scheduleDate): + return ("suggestedPost", [("flags", flags as Any), ("price", price as Any), ("scheduleDate", scheduleDate as Any)]) + } + } + + public static func parse_suggestedPost(_ reader: BufferReader) -> SuggestedPost? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarsAmount? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.SuggestedPost.suggestedPost(flags: _1!, price: _2, scheduleDate: _3) + } + else { + return nil + } + } + + } +} +public extension Api { + enum TextWithEntities: TypeConstructorDescription { + case textWithEntities(text: String, entities: [Api.MessageEntity]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .textWithEntities(let text, let entities): + if boxed { + buffer.appendInt32(1964978502) + } + serializeString(text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .textWithEntities(let text, let entities): + return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) + } + } + + public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum Theme: TypeConstructorDescription { case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) @@ -675,6 +1041,8 @@ public extension Api { case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) case updateSmsJob(jobId: String) + case updateStarGiftAuctionState(giftId: Int64, state: Api.StarGiftAuctionState) + case updateStarGiftAuctionUserState(giftId: Int64, userState: Api.StarGiftAuctionUserState) case updateStarsBalance(balance: Api.StarsAmount) case updateStarsRevenueStatus(peer: Api.Peer, status: Api.StarsRevenueStatus) case updateStickerSets(flags: Int32) @@ -1864,6 +2232,20 @@ public extension Api { } serializeString(jobId, buffer: buffer, boxed: false) break + case .updateStarGiftAuctionState(let giftId, let state): + if boxed { + buffer.appendInt32(1222788802) + } + serializeInt64(giftId, buffer: buffer, boxed: false) + state.serialize(buffer, true) + break + case .updateStarGiftAuctionUserState(let giftId, let userState): + if boxed { + buffer.appendInt32(-598150370) + } + serializeInt64(giftId, buffer: buffer, boxed: false) + userState.serialize(buffer, true) + break case .updateStarsBalance(let balance): if boxed { buffer.appendInt32(1317053305) @@ -2260,6 +2642,10 @@ public extension Api { return ("updateServiceNotification", [("flags", flags as Any), ("inboxDate", inboxDate as Any), ("type", type as Any), ("message", message as Any), ("media", media as Any), ("entities", entities as Any)]) case .updateSmsJob(let jobId): return ("updateSmsJob", [("jobId", jobId as Any)]) + case .updateStarGiftAuctionState(let giftId, let state): + return ("updateStarGiftAuctionState", [("giftId", giftId as Any), ("state", state as Any)]) + case .updateStarGiftAuctionUserState(let giftId, let userState): + return ("updateStarGiftAuctionUserState", [("giftId", giftId as Any), ("userState", userState as Any)]) case .updateStarsBalance(let balance): return ("updateStarsBalance", [("balance", balance as Any)]) case .updateStarsRevenueStatus(let peer, let status): @@ -4679,6 +5065,38 @@ public extension Api { return nil } } + public static func parse_updateStarGiftAuctionState(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.StarGiftAuctionState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStarGiftAuctionState(giftId: _1!, state: _2!) + } + else { + return nil + } + } + public static func parse_updateStarGiftAuctionUserState(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.StarGiftAuctionUserState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStarGiftAuctionUserState(giftId: _1!, userState: _2!) + } + else { + return nil + } + } public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? { var _1: Api.StarsAmount? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index bb3b303e57..49988fc94e 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1,3 +1,49 @@ +public extension Api { + enum BusinessGreetingMessage: TypeConstructorDescription { + case businessGreetingMessage(shortcutId: Int32, recipients: Api.BusinessRecipients, noActivityDays: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays): + if boxed { + buffer.appendInt32(-451302485) + } + serializeInt32(shortcutId, buffer: buffer, boxed: false) + recipients.serialize(buffer, true) + serializeInt32(noActivityDays, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessGreetingMessage(let shortcutId, let recipients, let noActivityDays): + return ("businessGreetingMessage", [("shortcutId", shortcutId as Any), ("recipients", recipients as Any), ("noActivityDays", noActivityDays as Any)]) + } + } + + public static func parse_businessGreetingMessage(_ reader: BufferReader) -> BusinessGreetingMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BusinessRecipients? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum BusinessIntro: TypeConstructorDescription { case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?) diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 0bbca3f94f..9a73b7be16 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -276,6 +276,64 @@ public extension Api.payments { } } +public extension Api.payments { + enum StarGiftAuctionState: TypeConstructorDescription { + case starGiftAuctionState(state: Api.StarGiftAuctionState, userState: Api.StarGiftAuctionUserState, timeout: Int32, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionState(let state, let userState, let timeout, let users): + if boxed { + buffer.appendInt32(-2061303084) + } + state.serialize(buffer, true) + userState.serialize(buffer, true) + serializeInt32(timeout, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGiftAuctionState(let state, let userState, let timeout, let users): + return ("starGiftAuctionState", [("state", state as Any), ("userState", userState as Any), ("timeout", timeout as Any), ("users", users as Any)]) + } + } + + public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { + var _1: Api.StarGiftAuctionState? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState + } + var _2: Api.StarGiftAuctionUserState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState + } + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.payments.StarGiftAuctionState.starGiftAuctionState(state: _1!, userState: _2!, timeout: _3!, users: _4!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum StarGiftCollections: TypeConstructorDescription { case starGiftCollections(collections: [Api.StarGiftCollection]) @@ -1630,89 +1688,3 @@ public extension Api.premium { } } -public extension Api.premium { - enum BoostsStatus: TypeConstructorDescription { - case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): - if boxed { - buffer.appendInt32(1230586490) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(level, buffer: buffer, boxed: false) - serializeInt32(currentLevelBoosts, buffer: buffer, boxed: false) - serializeInt32(boosts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(giftBoosts!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextLevelBoosts!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {premiumAudience!.serialize(buffer, true)} - serializeString(boostUrl, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(prepaidGiveaways!.count)) - for item in prepaidGiveaways! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(myBoostSlots!.count)) - for item in myBoostSlots! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): - return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("giftBoosts", giftBoosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any), ("prepaidGiveaways", prepaidGiveaways as Any), ("myBoostSlots", myBoostSlots as Any)]) - } - } - - public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } - var _7: Api.StatsPercentValue? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue - } } - var _8: String? - _8 = parseString(reader) - var _9: [Api.PrepaidGiveaway]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) - } } - var _10: [Int32]? - if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index 33ce343b54..273c78fed3 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -1,3 +1,89 @@ +public extension Api.premium { + enum BoostsStatus: TypeConstructorDescription { + case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): + if boxed { + buffer.appendInt32(1230586490) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(level, buffer: buffer, boxed: false) + serializeInt32(currentLevelBoosts, buffer: buffer, boxed: false) + serializeInt32(boosts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(giftBoosts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextLevelBoosts!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {premiumAudience!.serialize(buffer, true)} + serializeString(boostUrl, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(prepaidGiveaways!.count)) + for item in prepaidGiveaways! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(myBoostSlots!.count)) + for item in myBoostSlots! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let giftBoosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways, let myBoostSlots): + return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("giftBoosts", giftBoosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any), ("prepaidGiveaways", prepaidGiveaways as Any), ("myBoostSlots", myBoostSlots as Any)]) + } + } + + public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + var _7: Api.StatsPercentValue? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue + } } + var _8: String? + _8 = parseString(reader) + var _9: [Api.PrepaidGiveaway]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) + } } + var _10: [Int32]? + if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) + } + else { + return nil + } + } + + } +} public extension Api.premium { enum MyBoosts: TypeConstructorDescription { case myBoosts(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 5212a5e7b9..4f235a40c8 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -9754,6 +9754,22 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func getStarGiftAuctionState(giftId: Int64, version: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1352689229) + serializeInt64(giftId, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftAuctionState", parameters: [("giftId", String(describing: giftId)), ("version", String(describing: version))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftAuctionState? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftAuctionState? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftAuctionState + } + return result + }) + } +} public extension Api.functions.payments { static func getStarGiftCollections(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -10854,6 +10870,22 @@ public extension Api.functions.phone { }) } } +public extension Api.functions.phone { + static func saveDefaultSendAs(call: Api.InputGroupCall, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1097313745) + call.serialize(buffer, true) + sendAs.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveDefaultSendAs", parameters: [("call", String(describing: call)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.phone { static func sendConferenceCallBroadcast(call: Api.InputGroupCall, block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -10887,15 +10919,16 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(445465039) + buffer.appendInt32(-1311697904) serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) serializeInt64(randomId, buffer: buffer, boxed: false) message.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("randomId", String(describing: randomId)), ("message", String(describing: message)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 1) != 0 {sendAs!.serialize(buffer, true)} + return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("randomId", String(describing: randomId)), ("message", String(describing: message)), ("allowPaidStars", String(describing: allowPaidStars)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index d78b77465d..1ad2e7de78 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -1222,14 +1222,14 @@ public extension Api { } public extension Api { enum GroupCall: TypeConstructorDescription { - case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32, inviteLink: String?, sendPaidMessagesStars: Int64?) + case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, title: String?, streamDcId: Int32?, recordStartDate: Int32?, scheduleDate: Int32?, unmutedVideoCount: Int32?, unmutedVideoLimit: Int32, version: Int32, inviteLink: String?, sendPaidMessagesStars: Int64?, defaultSendAs: Api.Peer?) case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars): + case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars, let defaultSendAs): if boxed { - buffer.appendInt32(-674602536) + buffer.appendInt32(-273500649) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -1244,6 +1244,7 @@ public extension Api { serializeInt32(version, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 16) != 0 {serializeString(inviteLink!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 20) != 0 {serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 21) != 0 {defaultSendAs!.serialize(buffer, true)} break case .groupCallDiscarded(let id, let accessHash, let duration): if boxed { @@ -1258,8 +1259,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars): - return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any), ("inviteLink", inviteLink as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any)]) + case .groupCall(let flags, let id, let accessHash, let participantsCount, let title, let streamDcId, let recordStartDate, let scheduleDate, let unmutedVideoCount, let unmutedVideoLimit, let version, let inviteLink, let sendPaidMessagesStars, let defaultSendAs): + return ("groupCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("participantsCount", participantsCount as Any), ("title", title as Any), ("streamDcId", streamDcId as Any), ("recordStartDate", recordStartDate as Any), ("scheduleDate", scheduleDate as Any), ("unmutedVideoCount", unmutedVideoCount as Any), ("unmutedVideoLimit", unmutedVideoLimit as Any), ("version", version as Any), ("inviteLink", inviteLink as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("defaultSendAs", defaultSendAs as Any)]) case .groupCallDiscarded(let id, let accessHash, let duration): return ("groupCallDiscarded", [("id", id as Any), ("accessHash", accessHash as Any), ("duration", duration as Any)]) } @@ -1292,6 +1293,10 @@ public extension Api { if Int(_1!) & Int(1 << 16) != 0 {_12 = parseString(reader) } var _13: Int64? if Int(_1!) & Int(1 << 20) != 0 {_13 = reader.readInt64() } + var _14: Api.Peer? + if Int(_1!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1305,8 +1310,9 @@ public extension Api { let _c11 = _11 != nil let _c12 = (Int(_1!) & Int(1 << 16) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 20) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13) + let _c14 = (Int(_1!) & Int(1 << 21) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13, defaultSendAs: _14) } else { return nil diff --git a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift index 0fcc407820..24cfad99ba 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift @@ -520,7 +520,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { } #if DEBUG - if data.info.isStream { + if data.info.isStream, !"".isEmpty { if self.imageDisposable == nil { let engine = self.context.engine let info = data.info diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 8860268561..e3ba01b24a 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -3330,6 +3330,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) } + public func addExternalAudioData(data: Data) { + self.genericCallContext?.addExternalAudioData(data: data) + } + public func requestVideo() { if self.videoCapturer == nil { let videoCapturer = OngoingCallVideoCapturer() diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 2596eb6c16..779440d6ea 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -137,6 +137,8 @@ enum AccountStateMutationOperation { case UpdateStarsReactionsDefaultPrivacy(privacy: TelegramPaidReactionPrivacy) case ReportMessageDelivery([MessageId]) case UpdateMonoForumNoPaidException(peerId: PeerId, threadId: Int64, isFree: Bool) + case UpdateStarGiftAuctionState(giftId: Int64, state: GiftAuctionContext.State.AuctionState) + case UpdateStarGiftAuctionMyState(giftId: Int64, state: GiftAuctionContext.State.MyState) } struct HoleFromPreviousState { @@ -727,9 +729,17 @@ struct AccountMutableState { self.addOperation(.UpdateMonoForumNoPaidException(peerId: peerId, threadId: threadId, isFree: isFree)) } + mutating func updateStarGiftAuctionState(giftId: Int64, state: GiftAuctionContext.State.AuctionState) { + self.addOperation(.UpdateStarGiftAuctionState(giftId: giftId, state: state)) + } + + mutating func updateStarGiftAuctionMyState(giftId: Int64, state: GiftAuctionContext.State.MyState) { + self.addOperation(.UpdateStarGiftAuctionMyState(giftId: giftId, state: state)) + } + mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState: break case let .AddMessages(messages, location): for message in messages { @@ -880,6 +890,8 @@ struct AccountReplayedFinalState { let sentScheduledMessageIds: Set let reportMessageDelivery: Set let addedConferenceInvitationMessagesIds: [MessageId] + let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] + let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] } struct AccountFinalStateEvents { @@ -913,12 +925,14 @@ struct AccountFinalStateEvents { let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let reportMessageDelivery: Set let addedConferenceInvitationMessagesIds: [MessageId] + let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] + let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -949,6 +963,8 @@ struct AccountFinalStateEvents { self.sentScheduledMessageIds = sentScheduledMessageIds self.reportMessageDelivery = reportMessageDelivery self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds + self.updatedStarGiftAuctionState = updatedStarGiftAuctionState + self.updatedStarGiftAuctionMyState = updatedStarGiftAuctionMyState } init(state: AccountReplayedFinalState) { @@ -982,6 +998,8 @@ struct AccountFinalStateEvents { self.sentScheduledMessageIds = state.sentScheduledMessageIds self.reportMessageDelivery = state.reportMessageDelivery self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds + self.updatedStarGiftAuctionState = state.updatedStarGiftAuctionState + self.updatedStarGiftAuctionMyState = state.updatedStarGiftAuctionMyState } func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { @@ -1040,7 +1058,7 @@ struct AccountFinalStateEvents { updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, - updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates,uniquingKeysWith: { lhs, _ in lhs }), + updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), @@ -1048,7 +1066,9 @@ struct AccountFinalStateEvents { updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds, reportMessageDelivery: reportMessageDelivery, - addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds + addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds, + updatedStarGiftAuctionState: self.updatedStarGiftAuctionState.merging(other.updatedStarGiftAuctionState, uniquingKeysWith: { lhs, _ in lhs }), + updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs }) ) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 9cc3622a88..6a5c365629 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -111,7 +111,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .joinedByRequest) case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text): return TelegramMediaAction(action: .webViewData(text)) - case let .messageActionGiftPremium(_, currency, amount, months, cryptoCurrency, cryptoAmount, message): + case let .messageActionGiftPremium(_, currency, amount, days, cryptoCurrency, cryptoAmount, message): let text: String? let entities: [MessageTextEntity]? switch message { @@ -122,7 +122,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe text = nil entities = nil } - return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities)) + return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, days: days, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities)) case let .messageActionGiftStars(_, currency, amount, stars, cryptoCurrency, cryptoAmount, transactionId): return TelegramMediaAction(action: .giftStars(currency: currency, amount: amount, count: stars, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId)) case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 04e469776d..3dcd41bc0f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1900,6 +1900,12 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.updateStarsReactionsDefaultPrivacy(privacy: mappedPrivacy) case let .updateMonoForumNoPaidException(flags, channelId, savedPeerId): updatedState.updateMonoForumNoPaidException(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: savedPeerId.peerId.toInt64(), isFree: (flags & (1 << 0)) != 0) + case let .updateStarGiftAuctionState(giftId, state): + if let state = GiftAuctionContext.State.AuctionState(apiAuctionState: state) { + updatedState.updateStarGiftAuctionState(giftId: giftId, state: state) + } + case let .updateStarGiftAuctionUserState(giftId, userState): + updatedState.updateStarGiftAuctionMyState(giftId: giftId, state: GiftAuctionContext.State.MyState(apiAuctionUserState: userState)) default: break } @@ -3623,7 +3629,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3764,6 +3770,8 @@ func replayFinalState( var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:] var updatedStarsReactionsDefaultPrivacy: TelegramPaidReactionPrivacy? var reportMessageDelivery = Set() + var updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:] + var updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:] var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] @@ -4906,7 +4914,7 @@ func replayFinalState( } switch call { - case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _, sendPaidMessagesStars): + case let .groupCall(flags, _, _, participantsCount, title, _, recordStartDate, scheduleDate, _, _, _, _, sendPaidMessagesStars, _): let isMin = (flags & (1 << 19)) != 0 let isMuted = (flags & (1 << 1)) != 0 let canChange = (flags & (1 << 2)) != 0 @@ -5322,6 +5330,10 @@ func replayFinalState( transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } } + case let .UpdateStarGiftAuctionState(giftId, state): + updatedStarGiftAuctionState[giftId] = state + case let .UpdateStarGiftAuctionMyState(giftId, state): + updatedStarGiftAuctionMyState[giftId] = state } } @@ -5870,6 +5882,8 @@ func replayFinalState( updatedStarsRevenueStatus: updatedStarsRevenueStatus, sentScheduledMessageIds: finalState.state.sentScheduledMessageIds, reportMessageDelivery: reportMessageDelivery, - addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds + addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds, + updatedStarGiftAuctionState: updatedStarGiftAuctionState, + updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState ) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 3f61deae1c..e9cce703da 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -52,6 +52,14 @@ private final class UpdatedStarsRevenueStatusSubscriberContext { let subscribers = Bag<([PeerId: StarsRevenueStats.Balances]) -> Void>() } +private final class UpdatedStarGiftAuctionStateSubscriberContext { + let subscribers = Bag<([Int64: GiftAuctionContext.State.AuctionState]) -> Void>() +} + +private final class UpdatedStarGiftAuctionMyStateSubscriberContext { + let subscribers = Bag<([Int64: GiftAuctionContext.State.MyState]) -> Void>() +} + public enum DeletedMessageId: Hashable { case global(Int32) case messageId(MessageId) @@ -357,6 +365,8 @@ public final class AccountStateManager { private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedTonBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext() + private var updatedStarGiftAuctionStateContext = UpdatedStarGiftAuctionStateSubscriberContext() + private var updatedStarGiftAuctionMyStateContext = UpdatedStarGiftAuctionMyStateSubscriberContext() private let delayNotificatonsUntil = Atomic(value: nil) private let appliedMaxMessageIdPromise = Promise(nil) @@ -1121,6 +1131,12 @@ public final class AccountStateManager { if !events.updatedStarsRevenueStatus.isEmpty { strongSelf.notifyUpdatedStarsRevenueStatus(events.updatedStarsRevenueStatus) } + if !events.updatedStarGiftAuctionState.isEmpty { + strongSelf.notifyUpdatedStarGiftAuctionState(events.updatedStarGiftAuctionState) + } + if !events.updatedStarGiftAuctionMyState.isEmpty { + strongSelf.notifyUpdatedStarGiftAuctionMyState(events.updatedStarGiftAuctionMyState) + } if !events.updatedCalls.isEmpty { for call in events.updatedCalls { strongSelf.callSessionManager?.updateSession(call, completion: { _ in }) @@ -1788,6 +1804,60 @@ public final class AccountStateManager { subscriber(updatedStarsRevenueStatus) } } + + public func updatedStarGiftAuctionState() -> Signal<[Int64: GiftAuctionContext.State.AuctionState], NoError> { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let index = strongSelf.updatedStarGiftAuctionStateContext.subscribers.add({ starsBalance in + subscriber.putNext(starsBalance) + }) + + disposable.set(ActionDisposable { + if let strongSelf = self { + strongSelf.updatedStarGiftAuctionStateContext.subscribers.remove(index) + } + }) + } + } + return disposable + } + } + + private func notifyUpdatedStarGiftAuctionState(_ updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState]) { + for subscriber in self.updatedStarGiftAuctionStateContext.subscribers.copyItems() { + subscriber(updatedStarGiftAuctionState) + } + } + + public func updatedStarGiftAuctionMyState() -> Signal<[Int64: GiftAuctionContext.State.MyState], NoError> { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let index = strongSelf.updatedStarGiftAuctionMyStateContext.subscribers.add({ starsBalance in + subscriber.putNext(starsBalance) + }) + + disposable.set(ActionDisposable { + if let strongSelf = self { + strongSelf.updatedStarGiftAuctionMyStateContext.subscribers.remove(index) + } + }) + } + } + return disposable + } + } + + private func notifyUpdatedStarGiftAuctionMyState(_ updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState]) { + for subscriber in self.updatedStarGiftAuctionMyStateContext.subscribers.copyItems() { + subscriber(updatedStarGiftAuctionMyState) + } + } func notifyDeletedMessages(messageIds: [MessageId]) { self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) }) @@ -2162,6 +2232,19 @@ public final class AccountStateManager { } } + + public func updatedStarGiftAuctionState() -> Signal<[Int64: GiftAuctionContext.State.AuctionState], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.updatedStarGiftAuctionState().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + + public func updatedStarGiftAuctionMyState() -> Signal<[Int64: GiftAuctionContext.State.MyState], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.updatedStarGiftAuctionMyState().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + func addCustomOperation(_ f: Signal) -> Signal { return self.impl.signalWith { impl, subscriber in return impl.addCustomOperation(f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 0cb422ada2..cc3b70c115 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 217 + return 218 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index acebcb5f86..662fbc6cdd 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -227,7 +227,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case setChatTheme(chatTheme: ChatTheme) case joinedByRequest case webViewData(String) - case giftPremium(currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?) + case giftPremium(currency: String, amount: Int64, days: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?) case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?) case topicEdited(components: [ForumTopicEditComponent]) case suggestedProfilePhoto(image: TelegramMediaImage?) @@ -330,7 +330,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case 26: self = .webViewData(decoder.decodeStringForKey("t", orElse: "")) case 27: - self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities")) + self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), days: decoder.decodeInt32ForKey("days", orElse: decoder.decodeInt32ForKey("months", orElse: 0) * 30), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities")) case 28: self = .topicCreated(title: decoder.decodeStringForKey("title", orElse: ""), iconColor: decoder.decodeInt32ForKey("iconColor", orElse: 0), iconFileId: decoder.decodeOptionalInt64ForKey("iconFileId")) case 29: @@ -553,11 +553,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .webViewData(text): encoder.encodeInt32(26, forKey: "_rawValue") encoder.encodeString(text, forKey: "t") - case let .giftPremium(currency, amount, months, cryptoCurrency, cryptoAmount, text, entities): + case let .giftPremium(currency, amount, days, cryptoCurrency, cryptoAmount, text, entities): encoder.encodeInt32(27, forKey: "_rawValue") encoder.encodeString(currency, forKey: "currency") encoder.encodeInt64(amount, forKey: "amount") - encoder.encodeInt32(months, forKey: "months") + encoder.encodeInt32(days, forKey: "days") if let cryptoCurrency = cryptoCurrency, let cryptoAmount = cryptoAmount { encoder.encodeString(cryptoCurrency, forKey: "cryptoCurrency") encoder.encodeInt64(cryptoAmount, forKey: "cryptoAmount") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 06bd09fe43..f9e6dfab35 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -143,7 +143,7 @@ public struct GroupCallSummary: Equatable { extension GroupCallInfo { init?(_ call: Api.GroupCall) { switch call { - case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars): + case let .groupCall(flags, id, accessHash, participantsCount, title, streamDcId, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars, _): self.init( id: id, accessHash: accessHash, @@ -773,7 +773,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, maybeParsedCall = GroupCallInfo(call) switch call { - case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars): + case let .groupCall(flags, _, _, _, title, _, recordStartDate, scheduleDate, _, unmutedVideoLimit, _, _, sendPaidMessagesStars, _): let isMin = (flags & (1 << 19)) != 0 let isMuted = (flags & (1 << 1)) != 0 let canChange = (flags & (1 << 2)) != 0 @@ -4277,7 +4277,8 @@ public final class GroupCallMessagesContext { text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) ), - allowPaidStars: paidStars + allowPaidStars: paidStars, + sendAs: nil )) |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in guard let self else { return @@ -4330,7 +4331,8 @@ public final class GroupCallMessagesContext { text: "", entities: [] ), - allowPaidStars: pendingSendStars.amount + allowPaidStars: pendingSendStars.amount, + sendAs: nil )) |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in guard let self else { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 2d21de2fd2..8fc0db0404 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1112,11 +1112,12 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) { }).start() } -func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal { +func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, rtmp: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, messagesEnabled: Bool, sendPaidMessageStars: Int64?) -> Signal { return account.postbox.transaction { transaction in var flags: Int32 = 0 - //flags |= 1 << 5 - + if rtmp { + flags |= 1 << 5 + } if isForwardingDisabled { flags |= 1 << 4 } @@ -1128,7 +1129,14 @@ func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, pri inputPeer = .inputPeerSelf } - return account.network.request(Api.functions.stories.startLive(flags: flags, peer: inputPeer, caption: nil, entities: nil, privacyRules: [.inputPrivacyValueAllowAll], randomId: Int64.random(in: Int64.min ... Int64.max), messagesEnabled: nil, sendPaidMessagesStars: nil)) + flags |= 1 << 6 + if let sendPaidMessageStars, sendPaidMessageStars > 0 { + flags |= 1 << 7 + } + + let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction) + + return account.network.request(Api.functions.stories.startLive(flags: flags, peer: inputPeer, caption: nil, entities: nil, privacyRules: privacyRules, randomId: Int64.random(in: Int64.min ... Int64.max), messagesEnabled: messagesEnabled ? .boolTrue : .boolFalse, sendPaidMessagesStars: sendPaidMessageStars)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -1138,7 +1146,7 @@ func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, pri account.stateManager.addUpdates(updates) for update in updates.allUpdates { - if case let .updateStory(_, apiStory) = update { + if case let .updateStory(_, apiStory) = update, case .storyItem = apiStory { return account.postbox.transaction { transaction in if let storedItem = Stories.StoredItem(apiStoryItem: apiStory, peerId: peerId, transaction: transaction), case let .item(item) = storedItem, let media = item.media { let mappedItem = EngineStoryItem( diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index b28845b55f..dfbbebf1b1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1414,8 +1414,8 @@ public extension TelegramEngine { return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo) } - public func beginStoryLivestream(peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal { - return _internal_beginStoryLivestream(account: self.account, peerId: peerId, privacy: privacy, isForwardingDisabled: isForwardingDisabled) + public func beginStoryLivestream(peerId: EnginePeer.Id, rtmp: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, messagesEnabled: Bool, sendPaidMessageStars: Int64?) -> Signal { + return _internal_beginStoryLivestream(account: self.account, peerId: peerId, rtmp: rtmp, privacy: privacy, isForwardingDisabled: isForwardingDisabled, messagesEnabled: messagesEnabled, sendPaidMessageStars: sendPaidMessageStars) } public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index f65954b9c7..79df01e609 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -20,6 +20,7 @@ public enum BotPaymentInvoiceSource { case starGiftResale(slug: String, toPeerId: EnginePeer.Id, ton: Bool) case starGiftPrepaidUpgrade(peerId: EnginePeer.Id, hash: String) case starGiftDropOriginalDetails(reference: StarGiftReference) + case starGiftAuctionBid(hideName: Bool, updateBid: Bool, peerId: EnginePeer.Id, giftId: Int64, bidAmount: Int64, text: String?, entities: [MessageTextEntity]?) } public struct BotPaymentInvoiceFields: OptionSet { @@ -423,6 +424,24 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv return .inputInvoiceStarGiftPrepaidUpgrade(peer: inputPeer, hash: hash) case let .starGiftDropOriginalDetails(reference): return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftDropOriginalDetails(stargift: $0) } + + case let .starGiftAuctionBid(hideName, updateBid, peerId, giftId, bidAmount, text, entities): + guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else { + return nil + } + var flags: Int32 = 0 + if hideName { + flags |= (1 << 0) + } + if updateBid { + flags |= (1 << 2) + } + var message: Api.TextWithEntities? + if let text, !text.isEmpty { + flags |= (1 << 1) + message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? []) + } + return .inputInvoiceStarGiftAuctionBid(flags: flags, peer: inputPeer, giftId: giftId, bidAmount: bidAmount, message: message) } } @@ -765,7 +784,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid: receiptMessageId = nil } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 6e602c7f42..e4f55d6495 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -44,6 +44,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public static let isBirthdayGift = Flags(rawValue: 1 << 0) public static let requiresPremium = Flags(rawValue: 1 << 1) public static let peerColorAvailable = Flags(rawValue: 1 << 2) + public static let isAuction = Flags(rawValue: 1 << 3) } enum CodingKeys: String, CodingKey { @@ -970,6 +971,9 @@ extension StarGift { if (apiFlags & (1 << 10)) != 0 { flags.insert(.peerColorAvailable) } + if (apiFlags & (1 << 11)) != 0 { + flags.insert(.isAuction) + } var availability: StarGift.Gift.Availability? if let availabilityRemains, let availabilityTotal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift new file mode 100644 index 0000000000..d26a45ebd2 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -0,0 +1,207 @@ +import Foundation +import Postbox +import MtProtoKit +import SwiftSignalKit +import TelegramApi + +private func _internal_getStarGiftAuctionState(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id, giftId: Int64, version: Int32) -> Signal<(state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)?, NoError> { + return network.request(Api.functions.payments.getStarGiftAuctionState(giftId: giftId, version: version)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<(state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)?, NoError> in + guard let result else { + return .single(nil) + } + return postbox.transaction { transaction -> (state: GiftAuctionContext.State.AuctionState?, myState: GiftAuctionContext.State.MyState, timeout: Int32)? in + switch result { + case let .starGiftAuctionState(state, userState, timeout, users): + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users)) + + return ( + state: GiftAuctionContext.State.AuctionState(apiAuctionState: state), + myState: GiftAuctionContext.State.MyState(apiAuctionUserState: userState), + timeout: timeout + ) + } + } + } +} + +public final class GiftAuctionContext { + public struct State: Equatable { + public struct BidLevel: Equatable { + public var position: Int32 + public var amount: Int64 + public var date: Int32 + } + + public enum AuctionState: Equatable { + case ongoing(version: Int32, minBidAmount: Int64, bidLevels: [BidLevel], topBidders: [EnginePeer.Id], dropSize: Int32, nextDropDate: Int32, dropsLeft: Int32, dropsTotal: Int32) + case finished + } + + public struct MyState: Equatable { + public var bidAmount: Int64? + public var bidDate: Int32? + public var minBidAmount: Int64? + public var acquiredCount: Int32 + } + + public var auctionState: AuctionState + public var myState: MyState + } + + private let queue: Queue = .mainQueue() + private let account: Account + private let giftId: Int64 + + private let disposable = MetaDisposable() + + private var auctionState: State.AuctionState? + private var myState: State.MyState? + private var timeout: Int32? + + private var updateAuctionStateDisposable: Disposable? + private var updateMyStateDisposable: Disposable? + private var updateTimer: SwiftSignalKit.Timer? + + private let stateValue = Promise() + public var state: Signal { + return self.stateValue.get() + } + + public init(account: Account, giftId: Int64) { + self.account = account + self.giftId = giftId + + self.load() + + self.updateAuctionStateDisposable = (self.account.stateManager.updatedStarGiftAuctionState() + |> mapToSignal { updates in + if let update = updates[giftId] { + return .single(update) + } + return .complete() + } + |> deliverOnMainQueue).start(next: { [weak self] auctionState in + guard let self else { + return + } + if case let .ongoing(version, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion { + } else { + self.auctionState = auctionState + } + self.pushState() + }) + + self.updateMyStateDisposable = (self.account.stateManager.updatedStarGiftAuctionMyState() + |> mapToSignal { updates in + if let update = updates[giftId] { + return .single(update) + } + return .complete() + } + |> deliverOnMainQueue).start(next: { [weak self] myState in + guard let self else { + return + } + self.myState = myState + self.pushState() + }) + } + + deinit { + self.updateAuctionStateDisposable?.dispose() + self.updateMyStateDisposable?.dispose() + self.updateTimer?.invalidate() + self.disposable.dispose() + } + + private var currentVersion: Int32 { + var currentVersion: Int32 = 0 + if case let .ongoing(version, _, _, _, _, _, _, _) = self.auctionState { + currentVersion = version + } + return currentVersion + } + + public func load() { + self.pushState() + + self.disposable.set((_internal_getStarGiftAuctionState(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, giftId: self.giftId, version: self.currentVersion) + |> deliverOn(self.queue)).start(next: { [weak self] data in + guard let self else { + return + } + guard let (auctionState, myState, timeout) = data else { + return + } + + if case let .ongoing(version, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion { + } else if let auctionState { + self.auctionState = auctionState + } + self.myState = myState + self.timeout = timeout + + self.pushState() + + self.updateTimer?.invalidate() + self.updateTimer = SwiftSignalKit.Timer(timeout: Double(timeout), repeat: false, completion: { [weak self] _ in + guard let self else { + return + } + self.load() + }, queue: Queue.mainQueue()) + self.updateTimer?.start() + })) + } + + private func pushState() { + if let auctionState = self.auctionState, let myState = self.myState { + self.stateValue.set(.single( + State(auctionState: auctionState, myState: myState) + )) + } else { + self.stateValue.set(.single(nil)) + } + } +} + +extension GiftAuctionContext.State.BidLevel { + init(apiBidLevel: Api.AuctionBidLevel) { + switch apiBidLevel { + case let .auctionBidLevel(pos, amount, date): + self.position = pos + self.amount = amount + self.date = date + } + } +} + +extension GiftAuctionContext.State.AuctionState { + init?(apiAuctionState: Api.StarGiftAuctionState) { + switch apiAuctionState { + case let .starGiftAuctionState(version, minBidAmount, bidLevels, topBidders, dropSize, nextDropAt, dropsLeft, dropsTotal): + self = .ongoing(version: version, minBidAmount: minBidAmount, bidLevels: bidLevels.map(GiftAuctionContext.State.BidLevel.init(apiBidLevel:)), topBidders: topBidders.map { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, dropSize: dropSize, nextDropDate: nextDropAt, dropsLeft: dropsLeft, dropsTotal: dropsTotal) + case .starGiftAuctionStateFinished: + self = .finished + case .starGiftAuctionStateNotModified: + return nil + } + } +} + +extension GiftAuctionContext.State.MyState { + init(apiAuctionUserState: Api.StarGiftAuctionUserState) { + switch apiAuctionUserState { + case let .starGiftAuctionUserState(_, bidAmount, bidDate, minBidAmount, acquiredCount): + self.bidAmount = bidAmount + self.bidDate = bidDate + self.minBidAmount = minBidAmount + self.acquiredCount = acquiredCount + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 54b16a39bd..3c4e5f949f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1630,7 +1630,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid: receiptMessageId = nil } } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _) = action.action, case let .Id(messageId) = message.id { diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index f971bfaac9..cc44a9c264 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -27,7 +27,7 @@ public final class AnimatedTextComponent: Component { public enum Content: Equatable { case text(String) case number(Int, minDigits: Int) - case icon(String, offset: CGPoint) + case icon(String, tint: Bool, offset: CGPoint) } public var id: AnyHashable @@ -149,7 +149,7 @@ public final class AnimatedTextComponent: Component { } else { itemText = valueText.map(String.init) } - case let .icon(iconName, _): + case let .icon(iconName, _, _): let characterKey = CharacterKey(itemId: item.id, index: 0, value: iconName) validKeys.append(characterKey) } @@ -188,11 +188,11 @@ public final class AnimatedTextComponent: Component { for item in component.items { enum AnimatedTextCharacter { case text(String) - case icon(String, CGPoint) + case icon(String, Bool, CGPoint) var value: String { switch self { - case let .text(value), let .icon(value, _): + case let .text(value), let .icon(value, _, _): return value } } @@ -216,8 +216,8 @@ public final class AnimatedTextComponent: Component { } else { itemText = valueText.map { .text(String($0)) } } - case let .icon(iconName, offset): - itemText = [.icon(iconName, offset)] + case let .icon(iconName, tint, offset): + itemText = [.icon(iconName, tint, offset)] } var index = 0 for character in itemText { @@ -243,10 +243,10 @@ public final class AnimatedTextComponent: Component { font: component.font, color: component.color )) - case let .icon(iconName, offset): + case let .icon(iconName, tint, offset): characterComponent = AnyComponent(BundleIconComponent( name: iconName, - tintColor: component.color + tintColor: tint ? component.color : nil )) characterOffset = offset } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 9b19bbe670..3eaed92670 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -143,8 +143,8 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { theme: self.theme, strings: self.strings, metrics: layout.metrics, + safeInsets: layout.safeInsets, placeholder: self.strings.Attachment_FilesSearchPlaceholder, - resetText: nil, updated: { [weak self] query in guard let self else { return @@ -161,11 +161,11 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { ) ), environment: {}, - containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + containerSize: CGSize(width: layout.size.width, height: layout.size.height) ) let bottomInset: CGFloat = layout.insets(options: .input).bottom - let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + let searchInputFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { if searchInputView.superview == nil { self.view.addSubview(searchInputView) diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index b6124f5cba..cbc60c8848 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -536,7 +536,7 @@ public final class ButtonComponent: Component { transition: contentItemTransition, component: component.content.component, environment: {}, - containerSize: CGSize(width: availableSize.width - cornerRadius * 2.0, height: availableSize.height) + containerSize: CGSize(width: availableSize.width - cornerRadius, height: availableSize.height) ) if let contentView = contentItem.view.view { var animateIn = false diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index 19fc7867ba..bf24d14ddf 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -90,6 +90,7 @@ swift_library( "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/ShareWithPeersScreen", + "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramCallsUI", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift index 2fda91ee14..a7ada36fb3 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -10,6 +10,8 @@ import TelegramCallsUI import TelegramPresentationData import StoryContainerScreen import ChatEntityKeyboardInputNode +import AvatarNode +import MultilineTextComponent final class CameraLiveStreamComponent: Component { let context: AccountContext @@ -19,6 +21,7 @@ final class CameraLiveStreamComponent: Component { let story: EngineStoryItem? let statusBarHeight: CGFloat let inputHeight: CGFloat + let safeInsets: UIEdgeInsets let metrics: LayoutMetrics let deviceMetrics: DeviceMetrics let didSetupMediaStream: (PresentationGroupCall) -> Void @@ -31,6 +34,7 @@ final class CameraLiveStreamComponent: Component { story: EngineStoryItem?, statusBarHeight: CGFloat, inputHeight: CGFloat, + safeInsets: UIEdgeInsets, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, didSetupMediaStream: @escaping (PresentationGroupCall) -> Void @@ -42,6 +46,7 @@ final class CameraLiveStreamComponent: Component { self.story = story self.statusBarHeight = statusBarHeight self.inputHeight = inputHeight + self.safeInsets = safeInsets self.metrics = metrics self.deviceMetrics = deviceMetrics self.didSetupMediaStream = didSetupMediaStream @@ -69,6 +74,9 @@ final class CameraLiveStreamComponent: Component { if lhs.inputHeight != rhs.inputHeight { return false } + if lhs.safeInsets != rhs.safeInsets { + return false + } if lhs.metrics != rhs.metrics { return false } @@ -145,7 +153,7 @@ final class CameraLiveStreamComponent: Component { } let itemSetContainerInsets = UIEdgeInsets(top: component.statusBarHeight + 5.0, left: 0.0, bottom: 0.0, right: 0.0) - let itemSetContainerSafeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 20.0, right: 0.0) + let itemSetContainerSafeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 34.0, right: 0.0) let _ = liveChat.update( transition: mediaStreamTransition, @@ -188,10 +196,6 @@ final class CameraLiveStreamComponent: Component { // environment.controller()?.presentInGlobalOverlay(c, with: a) }, close: { - // guard let self, let environment = self.environment else { - // return - // } - // environment.controller()?.dismiss() }, navigate: { _ in }, @@ -253,3 +257,128 @@ final class CameraLiveStreamComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +public final class StreamAsComponent: Component { + let context: AccountContext + let peerId: EnginePeer.Id + + public init( + context: AccountContext, + peerId: EnginePeer.Id + ) { + self.context = context + self.peerId = peerId + } + + public static func ==(lhs: StreamAsComponent, rhs: StreamAsComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + return true + } + + public final class View: UIView { + private let avatarNode: AvatarNode + private let title = ComponentView() + private let subtitle = ComponentView() + private var arrow = UIImageView() + + private var component: StreamAsComponent? + private weak var state: EmptyComponentState? + + private var peer: EnginePeer? + + public override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 18.0)) + self.arrow.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: UIColor(white: 1.0, alpha: 0.8)) + + super.init(frame: frame) + + self.addSubnode(self.avatarNode) + self.addSubview(self.arrow) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func update(component: StreamAsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + if self.peer?.id != component.peerId { + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + self.peer = peer + self.state?.updated() + }) + } + + self.avatarNode.frame = CGRect(origin: .zero, size: CGSize(width: 32.0, height: 32.0)) + if let peer = self.peer { + self.avatarNode.setPeer( + context: component.context, + theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, + peer: peer, + synchronousLoad: true + ) + } + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: self.peer?.compactDisplayTitle ?? "", font: Font.semibold(14.0), textColor: .white, paragraphAlignment: .left)) + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 38.0, height: availableSize.height) + ) + let titleFrame = CGRect(origin: CGPoint(x: 42.0, y: 1.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: "change", font: Font.regular(11.0), textColor: UIColor(white: 1.0, alpha: 0.8), paragraphAlignment: .left)) + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 50.0, height: availableSize.height) + ) + let subtitleFrame = CGRect(origin: CGPoint(x: 42.0, y: titleFrame.maxY + 2.0), size: subtitleSize) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + subtitleView.frame = subtitleFrame + } + + if let icon = self.arrow.image { + self.arrow.frame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + 1.0, y: floorToScreenPixels(subtitleFrame.midY - icon.size.height / 2.0) + 1.0), size: icon.size).insetBy(dx: 1.0, dy: 1.0) + } + + return CGSize(width: max(titleFrame.maxX, subtitleFrame.maxX + 16.0), height: 32.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index aac00afa46..d20bcfa3ba 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -29,6 +29,7 @@ import ShareWithPeersScreen import TelegramVoip import TelegramCallsUI import GlassBarButtonComponent +import PlainButtonComponent let videoRedColor = UIColor(rgb: 0xff3b30) let collageGrids: [Camera.CollageGrid] = [ @@ -298,6 +299,23 @@ private final class CameraScreenComponent: CombinedComponent { private var volumeButtonsListener: VolumeButtonsListener? private let volumeButtonsListenerShouldBeActive = ValuePromise(false, ignoreRepeated: true) + + private var closeFriends = Promise<[EnginePeer]>() + private var adminedChannels = Promise<[EnginePeer]>() + private var storiesBlockedPeers: BlockedPeersContext? + + fileprivate var sendAsPeerId: EnginePeer.Id? + private var privacy: EngineStoryPrivacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []) + private var allowComments = true + private var isForwardingDisabled = false + private var pin = true + private var paidMessageStars: Int64 = 0 + + private(set) var liveStreamStory: EngineStoryItem? + private weak var liveStreamCall: PresentationGroupCall? + private var liveStreamVideoCapturer: OngoingCallVideoCapturer? + private var liveStreamVideoDisposable: Disposable? + private var liveStreamAudioDisposable: Disposable? var cameraState: CameraState? var swipeHint: CaptureControlsComponent.SwipeHint = .none @@ -352,6 +370,7 @@ private final class CameraScreenComponent: CombinedComponent { self.lastGalleryAssetsDisposable?.dispose() self.resultDisposable.dispose() self.liveStreamVideoDisposable?.dispose() + self.liveStreamAudioDisposable?.dispose() } func setupRecentAssetSubscription() { @@ -480,6 +499,12 @@ private final class CameraScreenComponent: CombinedComponent { return } + if mode == .live && self.storiesBlockedPeers == nil { + self.storiesBlockedPeers = BlockedPeersContext(account: self.context.account, subject: .stories) + self.adminedChannels.set(.single([]) |> then(self.context.engine.peers.channelsForStories())) + self.closeFriends.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends())) + } + controller.updateCameraState({ $0.updatedMode(mode) }, transition: .spring(duration: 0.3)) var flashOn = controller.cameraState.flashMode == .on @@ -873,41 +898,283 @@ private final class CameraScreenComponent: CombinedComponent { controller.updateCameraState({ $0.updatedRecording(.handsFree) }, transition: .spring(duration: 0.4)) } - private(set) var liveStreamStory: EngineStoryItem? - func startLiveStream() { + fileprivate func presentStreamAsPeer() { + let _ = combineLatest( + queue: Queue.mainQueue(), + self.adminedChannels.get(), + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + ).start(next: { [weak self] sendAsPeers, accountPeer in + guard let self, let accountPeer else { + return + } + var peers = [accountPeer] + peers.append(contentsOf: sendAsPeers) + + let stateContext = ShareWithPeersScreen.StateContext( + context: self.context, + subject: .peers(peers: peers, peerId: self.sendAsPeerId), + liveStream: true, + editing: false + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self, let controller = self.getController() else { + return + } + let peersController = ShareWithPeersScreen( + context: self.context, + initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), + stateContext: stateContext, + completion: { _, _, _, _, _, _, _ in }, + editCategory: { _, _, _, _ in }, + editBlockedPeers: { _, _, _, _ in }, + peerCompletion: { [weak self] peerId in + guard let self else { + return + } + self.sendAsPeerId = peerId + self.updated() + } + ) + controller.push(peersController) + }) + }) + } + + fileprivate func presentLiveSettings() { + let stateContext = LiveStreamSettingsScreen.StateContext( + context: self.context, + mode: .create( + sendAsPeerId: self.sendAsPeerId, + privacy: self.privacy, + allowComments: self.allowComments, + isForwardingDisabled: self.isForwardingDisabled, + pin: self.pin, + paidMessageStars: self.paidMessageStars + ), + closeFriends: self.closeFriends.get(), + adminedChannels: self.adminedChannels.get(), + blockedPeersContext: self.storiesBlockedPeers + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + guard let self, let controller = self.getController() else { + return + } + let settingsController = LiveStreamSettingsScreen( + context: self.context, + stateContext: stateContext, + editCategory: { [weak self] privacy, allowComments, isForwardingDisabled, pin, paidMessageStars in + guard let self else { + return + } + self.openEditCategory(privacy: privacy, blockedPeers: false, completion: { [weak self] privacy in + guard let self else { + return + } + self.privacy = privacy + self.allowComments = allowComments + self.isForwardingDisabled = isForwardingDisabled + self.pin = pin + self.paidMessageStars = paidMessageStars + self.presentLiveSettings() + }) + }, + editBlockedPeers: { [weak self] privacy, allowComments, isForwardingDisabled, pin, paidMessageStars in + guard let self else { + return + } + self.openEditCategory(privacy: privacy, blockedPeers: true, completion: { [weak self] privacy in + guard let self else { + return + } + self.allowComments = allowComments + self.isForwardingDisabled = isForwardingDisabled + self.pin = pin + self.paidMessageStars = paidMessageStars + self.presentLiveSettings() + }) + }, + completion: { [weak self] result in + guard let self else { + return + } + self.sendAsPeerId = result.sendAsPeerId + self.privacy = result.privacy + self.allowComments = result.allowComments + self.isForwardingDisabled = result.isForwardingDisabled + self.pin = result.pin + self.paidMessageStars = result.paidMessageStars + if result.startRtmpStream { + self.startLiveStream(rtmp: true) + } + } + ) + controller.push(settingsController) + }) + } + + private func openEditCategory(privacy: EngineStoryPrivacy, blockedPeers: Bool, completion: @escaping (EngineStoryPrivacy) -> Void) { + let subject: ShareWithPeersScreen.StateContext.Subject + if blockedPeers { + subject = .chats(blocked: true) + } else if privacy.base == .nobody { + subject = .chats(blocked: false) + } else { + subject = .contacts(base: privacy.base) + } + let stateContext = ShareWithPeersScreen.StateContext( + context: self.context, + subject: subject, + editing: false, + initialPeerIds: Set(privacy.additionallyIncludePeers), + blockedPeersContext: self.storiesBlockedPeers + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self, let controller = self.getController() else { + return + } + let categoryController = ShareWithPeersScreen( + context: self.context, + initialPrivacy: privacy, + stateContext: stateContext, + completion: { [weak self] _, result, _, _, peers, _, completed in + guard let self, completed else { + return + } + if blockedPeers { + let _ = self.storiesBlockedPeers?.updatePeerIds(result.additionallyIncludePeers).start() + completion(privacy) + } else if case .closeFriends = privacy.base { + let _ = self.context.engine.privacy.updateCloseFriends(peerIds: result.additionallyIncludePeers).start() + self.closeFriends.set(.single(peers)) + completion(EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: [])) + } else { + completion(result) + } + }, + editCategory: { _, _, _, _ in }, + editBlockedPeers: { _, _, _, _ in } + ) + controller.push(categoryController) + }) + } + + func startLiveStream(rtmp: Bool) { guard let controller = self.getController() else { return } - let _ = (self.context.engine.messages.beginStoryLivestream(peerId: self.context.account.peerId, privacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), isForwardingDisabled: false) - |> deliverOnMainQueue).start(next: { [weak self, weak controller] story in - guard let self else { + let peerId = self.sendAsPeerId ?? self.context.account.peerId + + let startNewLiveStream = { [weak self, weak controller] in + guard let self, let controller else { return } - self.liveStreamStory = story - controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) - self.updated(transition: .immediate) - }) + + if rtmp { + controller.node.pauseCameraCapture() + } + + let _ = (self.context.engine.messages.beginStoryLivestream(peerId: peerId, rtmp: rtmp, privacy: self.privacy, isForwardingDisabled: self.isForwardingDisabled, messagesEnabled: self.allowComments, sendPaidMessageStars: self.paidMessageStars) + |> deliverOnMainQueue).start(next: { [weak self, weak controller] story in + guard let self else { + return + } + self.liveStreamStory = story + controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) + self.updated(transition: .immediate) + }) + } + +// let _ = (self.context.engine.messages.storySubscriptions(isHidden: false) +// |> take(1) +// |> deliverOnMainQueue).start(next: { [weak self, weak controller] subscriptions in +// guard let self else { +// return +// } +// if subscriptions.accountItem?.hasLiveItems == true { +// let storyList = PeerExpiringStoryListContext(account: self.context.account, peerId: peerId) +// let _ = (storyList.state +// |> filter { !$0.isLoading } +// |> take(1) +// |> deliverOnMainQueue).start(next: { [weak self, weak controller] state in +// guard let self else { +// return +// } +// for item in state.items.reversed() { +// let _ = (self.context.engine.messages.getStory(peerId: peerId, id: item.id) +// |> deliverOnMainQueue).start(next: { [weak self, weak controller] item in +// guard let self, let item else { +// return +// } +// if case .liveStream = item.media { +// self.liveStreamStory = item +// controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) +// self.updated(transition: .immediate) +// return +// } +// }) +// } +// startNewStream() +// }) +// } else { + startNewLiveStream() +// } +// }) + } + + func endLiveStream() { + guard let controller = self.getController() else { + return + } + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let alertController = textAlertController( + context: self.context, + forceTheme: defaultDarkColorPresentationTheme, + title: "End Live Stream", + text: "Are you sure you want to end this live stream?", + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: "End", action: { [weak self, weak controller] in + guard let self, let controller else { + return + } + + let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone() + controller.dismiss(animated: true) + }) + ] + ) + controller.present(alertController, in: .window(.root)) } - private var liveStreamVideoCapturer: OngoingCallVideoCapturer? - private var liveStreamVideoDisposable: Disposable? func setupStreamCamera(call: PresentationGroupCall) { guard self.liveStreamVideoCapturer == nil, let call = call as? PresentationGroupCallImpl, let controller = self.getController() else { return } - let cameraVideoSource = controller.node.cameraVideoSource + self.liveStreamCall = call + + let liveStreamMediaSource = controller.node.liveStreamMediaSource let videoCapturer = OngoingCallVideoCapturer(keepLandscape: false, isCustom: true) self.liveStreamVideoCapturer = videoCapturer - self.liveStreamVideoDisposable = cameraVideoSource.addOnUpdated { [weak self, weak cameraVideoSource] in - guard let self, let cameraVideoSource, let videoCapturer = self.liveStreamVideoCapturer else { + self.liveStreamVideoDisposable = liveStreamMediaSource.addOnVideoUpdated { [weak self, weak liveStreamMediaSource] in + guard let self, let liveStreamMediaSource, let videoCapturer = self.liveStreamVideoCapturer else { return } - if let currentOutput = cameraVideoSource.currentOutput, let pixelBuffer = currentOutput.dataBuffer.pixelBuffer, let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: pixelBuffer) { + if let pixelBuffer = liveStreamMediaSource.currentVideoOutput, let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: pixelBuffer) { videoCapturer.injectSampleBuffer(sampleBuffer, rotation: .up, completion: {}) } } + self.liveStreamAudioDisposable = liveStreamMediaSource.addOnAudioUpdated { [weak self, weak liveStreamMediaSource] in + guard let self, let liveStreamMediaSource, let call = self.liveStreamCall as? PresentationGroupCallImpl else { + return + } + if let audioData = liveStreamMediaSource.currentAudioOutput { + call.addExternalAudioData(data: audioData) + } + } + Queue.mainQueue().after(1.0) { call.requestVideo(capturer: videoCapturer, useFrontCamera: false) } @@ -942,6 +1209,7 @@ private final class CameraScreenComponent: CombinedComponent { let endStreamButton = Child(GlassBarButtonComponent.self) let captureControls = Child(CaptureControlsComponent.self) let zoomControl = Child(ZoomComponent.self) + let streamAsButton = Child(PlainButtonComponent.self) let flashButton = Child(CameraButton.self) let flipButton = Child(CameraButton.self) let dualButton = Child(CameraButton.self) @@ -1135,7 +1403,7 @@ private final class CameraScreenComponent: CombinedComponent { case .video: state.startVideoRecording(pressing: false) case .live: - state.startLiveStream() + state.startLiveStream(rtmp: false) } } else { state.stopVideoRecording() @@ -1173,11 +1441,10 @@ private final class CameraScreenComponent: CombinedComponent { controller.presentGallery() } }, - settingsTapped: { - guard let controller = environment.controller() as? CameraScreenImpl else { - return + settingsTapped: { [weak state] in + if let state { + state.presentLiveSettings() } - controller.presentLiveSettings() }, swipeHintUpdated: { [weak state] hint in if let state { @@ -1229,6 +1496,7 @@ private final class CameraScreenComponent: CombinedComponent { story: state.liveStreamStory, statusBarHeight: environment.statusBarHeight, inputHeight: environment.inputHeight, + safeInsets: environment.safeInsets, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, didSetupMediaStream: { [weak state] call in @@ -1247,6 +1515,30 @@ private final class CameraScreenComponent: CombinedComponent { let topControlSideInset: CGFloat = 9.0 let topControlVerticalInset: CGFloat = 12.0 let topButtonSpacing: CGFloat = 15.0 + + if component.cameraState.mode == .live && !component.cameraState.isStreaming { + let streamAsButton = streamAsButton.update( + component: PlainButtonComponent( + content: AnyComponent( + StreamAsComponent(context: component.context, peerId: state.sendAsPeerId ?? component.context.account.peerId) + ), + action: { [weak state] in + if let state { + state.presentStreamAsPeer() + } + }, + animateAlpha: true, + animateScale: false + ), + availableSize: CGSize(width: 200.0, height: 40.0), + transition: .immediate + ) + context.add(streamAsButton + .position(CGPoint(x: topControlSideInset + streamAsButton.size.width / 2.0 + 7.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + streamAsButton.size.height / 2.0 + 4.0)) + .appear(.default(scale: true)) + .disappear(.default(scale: true)) + ) + } if component.cameraState.isStreaming { let endStreamButton = endStreamButton.update( @@ -1256,11 +1548,10 @@ private final class CameraScreenComponent: CombinedComponent { isDark: true, state: .glass, component: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "End", font: Font.semibold(17.0), color: .white))), - action: { _ in - guard let controller = controller() as? CameraScreenImpl else { - return + action: { [weak state] _ in + if let state { + state.endLiveStream() } - controller.requestDismiss(animated: true) } ), availableSize: CGSize(width: 40.0, height: 40.0), @@ -1921,12 +2212,25 @@ public class CameraScreenImpl: ViewController, CameraScreen { return current } else { let cameraVideoSource = CameraVideoSource() - self.camera?.setVideoOutput(cameraVideoSource.cameraVideoOutput) + self.camera?.setMainVideoOutput(cameraVideoSource.cameraVideoOutput) self._cameraVideoSource = cameraVideoSource return cameraVideoSource } } + private var _livestreamMediaSource: LiveStreamMediaSource? + var liveStreamMediaSource: LiveStreamMediaSource { + if let current = self._livestreamMediaSource { + return current + } else { + let livestreamMediaSource = LiveStreamMediaSource() + self.camera?.setMainVideoOutput(livestreamMediaSource.mainVideoOutput) + self.camera?.setAdditionalVideoOutput(livestreamMediaSource.additionalVideoOutput) + self._livestreamMediaSource = livestreamMediaSource + return livestreamMediaSource + } + } + var cameraState: CameraState { didSet { let previousPosition = oldValue.position @@ -3967,41 +4271,6 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.requestLayout(transition: .immediate) } - func presentLiveSettings() { - let stateContext = ShareWithPeersScreen.StateContext( - context: self.context, - subject: .stories(editing: false, count: 1) - ) - let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in - guard let self else { - return - } - let controller = ShareWithPeersScreen( - context: self.context, - initialPrivacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), - stateContext: stateContext, - completion: { sendAsPeerId, privacy, allowScreenshots, pin, _, folders, completed in - - }, - editCategory: { privacy, allowScreenshots, pin, folders in - }, - editBlockedPeers: { privacy, allowScreenshots, pin, folders in - - } - ) -// controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in -// if let self, let controller { -// let transitionFactor = controller.modalStyleOverlayTransitionFactor -// self.node.updateModalTransitionFactor(transitionFactor, transition: transition) -// } -// } -// controller.dismissed = { -// self.node.mediaEditor?.play() -// } - self.push(controller) - }) - } - public func presentDraftTooltip() { self.node.presentDraftTooltip() } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift index 92bf55b9cd..71327b888c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift @@ -5,6 +5,8 @@ import Display import SwiftSignalKit import Camera import MetalEngine +import MediaEditor +import TelegramCore final class CameraVideoSource: VideoSource { private var device: MTLDevice @@ -80,3 +82,165 @@ final class CameraVideoSource: VideoSource { } } } + + +final class LiveStreamMediaSource { + private let pool: CVPixelBufferPool? + + private(set) var mainVideoOutput: CameraVideoOutput! + private(set) var additionalVideoOutput: CameraVideoOutput! + private let composer: MediaEditorComposer + + private var currentMainSampleBuffer: CMSampleBuffer? + private var currentAdditionalSampleBuffer: CMSampleBuffer? + + public private(set) var currentVideoOutput: CVPixelBuffer? + private var onVideoUpdatedListeners = Bag<() -> Void>() + + public private(set) var currentAudioOutput: Data? + private var onAudioUpdatedListeners = Bag<() -> Void>() + + public init() { + let width: Int32 = 1080 + let height: Int32 = 1920 + + let dimensions = CGSize(width: CGFloat(width), height: CGFloat(height)) + + let bufferOptions: [String: Any] = [ + kCVPixelBufferPoolMinimumBufferCountKey as String: 3 as NSNumber + ] + let pixelBufferOptions: [String: Any] = [ + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA as NSNumber, + kCVPixelBufferWidthKey as String: UInt32(width), + kCVPixelBufferHeightKey as String: UInt32(height) + ] + + var pool: CVPixelBufferPool? + CVPixelBufferPoolCreate(nil, bufferOptions as CFDictionary, pixelBufferOptions as CFDictionary, &pool) + self.pool = pool + + let topOffset = CGPoint(x: 267.0, y: 438.0) + let additionalVideoPosition = CGPoint(x: dimensions.width - topOffset.x, y: topOffset.y) + + self.composer = MediaEditorComposer( + postbox: nil, + values: MediaEditorValues( + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), + originalDimensions: PixelDimensions(dimensions), + cropOffset: .zero, + cropRect: CGRect(origin: .zero, size: dimensions), + cropScale: 1.0, + cropRotation: 0.0, + cropMirroring: false, + cropOrientation: nil, + gradientColors: nil, + videoTrimRange: nil, + videoIsMuted: false, + videoIsFullHd: false, + videoIsMirrored: false, + videoVolume: nil, + additionalVideoPath: nil, + additionalVideoIsDual: true, + additionalVideoPosition: additionalVideoPosition, + additionalVideoScale: 1.625, + additionalVideoRotation: 0.0, + additionalVideoPositionChanges: [], + additionalVideoTrimRange: nil, + additionalVideoOffset: nil, + additionalVideoVolume: nil, + collage: [], + nightTheme: false, + drawing: nil, + maskDrawing: nil, + entities: [], + toolValues: [:], + audioTrack: nil, + audioTrackTrimRange: nil, + audioTrackOffset: nil, + audioTrackVolume: nil, + audioTrackSamples: nil, + collageTrackSamples: nil, + coverImageTimestamp: nil, + coverDimensions: nil, + qualityPreset: nil + ), + dimensions: CGSize(width: 1080.0, height: 1920.0), + outputDimensions: CGSize(width: 1080.0, height: 1920.0), + textScale: 1.0, + videoDuration: nil, + additionalVideoDuration: nil + ) + + self.mainVideoOutput = CameraVideoOutput(sink: { [weak self] buffer, mirror in + guard let self else { + return + } + self.currentMainSampleBuffer = try? CMSampleBuffer(copying: buffer) + self.push(mainSampleBuffer: buffer, additionalSampleBuffer: self.currentAdditionalSampleBuffer) + }) + + self.additionalVideoOutput = CameraVideoOutput(sink: { [weak self] buffer, mirror in + guard let self else { + return + } + self.currentAdditionalSampleBuffer = try? CMSampleBuffer(copying: buffer) + }) + } + + public func addOnVideoUpdated(_ f: @escaping () -> Void) -> Disposable { + let index = self.onVideoUpdatedListeners.add(f) + + return ActionDisposable { [weak self] in + DispatchQueue.main.async { + guard let self else { + return + } + self.onVideoUpdatedListeners.remove(index) + } + } + } + + public func addOnAudioUpdated(_ f: @escaping () -> Void) -> Disposable { + let index = self.onAudioUpdatedListeners.add(f) + + return ActionDisposable { [weak self] in + DispatchQueue.main.async { + guard let self else { + return + } + self.onAudioUpdatedListeners.remove(index) + } + } + } + private func push(mainSampleBuffer: CMSampleBuffer, additionalSampleBuffer: CMSampleBuffer?) { + let timestamp = mainSampleBuffer.presentationTimeStamp + + guard let mainPixelBuffer = CMSampleBufferGetImageBuffer(mainSampleBuffer) else { + return + } + let main: MediaEditorComposer.Input = .videoBuffer(VideoPixelBuffer(pixelBuffer: mainPixelBuffer, rotation: .rotate90Degrees, timestamp: timestamp), nil, 1.0, .zero) + var additional: [MediaEditorComposer.Input?] = [] + if let additionalPixelBuffer = additionalSampleBuffer.flatMap({ CMSampleBufferGetImageBuffer($0) }) { + additional.append( + .videoBuffer(VideoPixelBuffer(pixelBuffer: additionalPixelBuffer, rotation: .rotate90DegreesMirrored, timestamp: timestamp), nil, 1.0, .zero) + ) + } + self.composer.process( + main: main, + additional: additional, + timestamp: timestamp, + pool: self.pool, + completion: { [weak self] pixelBuffer in + guard let self else { + return + } + Queue.mainQueue().async { + self.currentVideoOutput = pixelBuffer + for onUpdated in self.onVideoUpdatedListeners.copyItems() { + onUpdated() + } + } + } + ) + } +} diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 756d177aea..40d18576a0 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -1190,6 +1190,7 @@ final class CaptureControlsComponent: Component { let hideControls = component.hideControls let galleryButtonFrame: CGRect + let lockReferenceFrame: CGRect let gallerySize: CGSize if component.hasGallery { let galleryCornerRadius: CGFloat @@ -1231,12 +1232,10 @@ final class CaptureControlsComponent: Component { ) if component.isTablet { galleryButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - galleryButtonSize.width) / 2.0), y: size.height - galleryButtonSize.height - 56.0), size: galleryButtonSize) + lockReferenceFrame = .zero } else { - if "".isEmpty { - galleryButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: size.height + 21.0), size: galleryButtonSize) - } else { - galleryButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize) - } + galleryButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: size.height + 21.0), size: galleryButtonSize) + lockReferenceFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize) } if let galleryButtonView = self.galleryButtonView.view as? CameraButton.View { galleryButtonView.contentView.clipsToBounds = true @@ -1258,6 +1257,7 @@ final class CaptureControlsComponent: Component { } else { galleryButtonFrame = .zero gallerySize = .zero + lockReferenceFrame = .zero } if !component.isTablet && component.hasAccess { @@ -1288,13 +1288,6 @@ final class CaptureControlsComponent: Component { containerSize: availableSize ) let flipButtonFrame = CGRect(origin: CGPoint(x: flipButtonOriginX, y: (size.height - flipButtonSize.height) / 2.0), size: flipButtonSize) - //if "".isEmpty { - // flipButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - flipButtonSize.width - 16.0, y: size.height + 21.0), size: flipButtonSize) - //} - - //self.flipButtonBackgroundView.update(size: CGSize(width: 48.0, height: 48.0), cornerRadius: 24.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.06)), transition: .immediate) - //self.flipButtonBackgroundView.frame = flipButtonFrame.insetBy(dx: -2.0, dy: -2.0).offsetBy(dx: 2.0, dy: 2.0) - if let flipButtonView = self.flipButtonView.view { if flipButtonView.superview == nil { self.addSubview(flipButtonView) @@ -1306,7 +1299,9 @@ final class CaptureControlsComponent: Component { transition.setAlpha(view: flipButtonView, alpha: !isRecording || isTransitioning || hideControls ? 0.0 : 1.0) } - self.bottomContainerView.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: availableSize.width, height: 21.0 + 64.0)) + let bottomContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: availableSize.width, height: 21.0 + 64.0)) + self.bottomContainerView.frame = bottomContainerFrame + self.bottomContainerView.update(size: bottomContainerFrame.size, isDark: true, transition: .immediate) let bottomFlipButtonSize = self.bottomFlipButton.update( transition: .immediate, @@ -1499,7 +1494,7 @@ final class CaptureControlsComponent: Component { lockMaskFrame = lockMaskFrame.offsetBy(dx: 0.0, dy: -8.0) } } else { - lockFrame = galleryButtonFrame.insetBy(dx: (gallerySize.width - hintIconSize.width) / 2.0, dy: (gallerySize.height - hintIconSize.height) / 2.0) + lockFrame = lockReferenceFrame.insetBy(dx: (gallerySize.width - hintIconSize.width) / 2.0, dy: (gallerySize.height - hintIconSize.height) / 2.0) lockMaskFrame = CGRect(origin: CGPoint(x: availableSize.width / 2.0 - lockFrame.midX - 9.0 + self.shutterOffsetX, y: -9.0), size: CGSize(width: 48.0, height: 48.0)) if self.panBlobState == .transientToLock { lockMaskFrame = lockMaskFrame.offsetBy(dx: 8.0, dy: 0.0) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift index be75072bb3..70ede1050c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift @@ -224,10 +224,12 @@ final class ModeComponent: Component { totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalSize.width) / 2.0), y: 0.0), size: totalSize)) } - transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: .zero, size: self.backgroundView.frame.size)) + + let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size) + transition.setFrame(view: self.glassContainerView, frame: containerFrame) let selectionFrame = selectedFrame.insetBy(dx: -20.0, dy: 3.0) - self.glassContainerView.alpha = 0.5 + self.glassContainerView.update(size: containerFrame.size, isDark: true, transition: .immediate) self.selectionView.update(size: selectionFrame.size, cornerRadius: selectionFrame.height * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.16)), transition: transition) transition.setFrame(view: self.selectionView, frame: selectionFrame) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 7b352664a7..37d39d7082 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -452,8 +452,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { for media in item.message.media { if let action = media as? TelegramMediaAction { switch action.action { - case let .giftPremium(_, _, monthsValue, _, _, giftText, giftEntities): - months = monthsValue + case let .giftPremium(_, _, daysValue, _, _, giftText, giftEntities): + months = max(3, Int32(round(Float(daysValue) / 30.0))) if months == 12 { title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1) } else { diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index bac2dae08b..2a8d1fa17a 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -228,7 +228,7 @@ final class ComposeTodoScreenComponent: Component { let theme = environment.theme.withModalBlocksBackground() let backgroundView = UIImageView(image: generateReorderingBackgroundImage(backgroundColor: theme.list.itemBlocksBackgroundColor)) - backgroundView.frame = wrapperView.bounds.insetBy(dx: -10.0, dy: -10.0) + backgroundView.frame = wrapperView.bounds.insetBy(dx: -16.0, dy: -16.0) snapshotView.frame = snapshotView.bounds wrapperView.addSubview(backgroundView) diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift index 280b539caa..ddf9b8bb28 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -42,16 +42,13 @@ final class NewContactScreenComponent: Component { let context: AccountContext let initialData: NewContactScreen.InitialData - let completion: (TelegramMediaTodo) -> Void init( context: AccountContext, - initialData: NewContactScreen.InitialData, - completion: @escaping (TelegramMediaTodo) -> Void + initialData: NewContactScreen.InitialData ) { self.context = context self.initialData = initialData - self.completion = completion } static func ==(lhs: NewContactScreenComponent, rhs: NewContactScreenComponent) -> Bool { @@ -94,6 +91,8 @@ final class NewContactScreenComponent: Component { private let phoneTag = NSObject() private let noteTag = NSObject() + private var updateFocusTag: Any? + private var syncContactToPhone = true private var cachedChevronImage: (UIImage, PresentationTheme)? @@ -251,21 +250,30 @@ final class NewContactScreenComponent: Component { let theme = environment.theme var initialCountryCode: Int32? - var initialFocusTag: Any? + var updateFocusTag: Any? if self.component == nil { + if let peer = component.initialData.peer { + self.resolvedPeer = .peer(peer: peer, isContact: false) + } + let countryCode: Int32 if let phone = component.initialData.phoneNumber { if let (_, code) = lookupCountryIdByNumber(phone, configuration: component.context.currentCountriesConfiguration.with { $0 }), let codeValue = Int32(code.code) { countryCode = codeValue + } else if phone.hasPrefix("999") { + countryCode = 93 } else { countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() } - initialFocusTag = self.firstNameTag + updateFocusTag = self.firstNameTag } else { countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() - initialFocusTag = self.phoneTag + updateFocusTag = self.phoneTag } initialCountryCode = countryCode + } else { + updateFocusTag = self.updateFocusTag + self.updateFocusTag = nil } self.component = component @@ -304,8 +312,15 @@ final class NewContactScreenComponent: Component { placeholder: "First Name", autocapitalizationType: .sentences, autocorrectionType: .default, + returnKeyType: .next, updated: { value in - + }, + onReturn: { [weak self] in + guard let self else { + return + } + self.updateFocusTag = self.lastNameTag + self.state?.updated() }, tag: self.firstNameTag ))), @@ -317,8 +332,15 @@ final class NewContactScreenComponent: Component { placeholder: "Last Name", autocapitalizationType: .sentences, autocorrectionType: .default, + returnKeyType: .next, updated: { value in - + }, + onReturn: { [weak self] in + guard let self else { + return + } + self.updateFocusTag = self.phoneTag + self.state?.updated() }, tag: self.lastNameTag ))) @@ -483,11 +505,15 @@ final class NewContactScreenComponent: Component { } }, tapAction: { [weak self] _, _ in - guard let self else { + guard let self, let component = self.component else { return } if case let .peer(peer, _) = self.resolvedPeer { - let _ = peer + if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let navigationController = component.context.sharedContext.mainWindow?.viewController as? NavigationController { + navigationController.pushViewController(infoController) + } + } } else { self.sendInvite() } @@ -825,8 +851,8 @@ final class NewContactScreenComponent: Component { transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } - if let initialFocusTag { - self.activateInput(tag: initialFocusTag) + if let updateFocusTag { + self.activateInput(tag: updateFocusTag) } return availableSize @@ -844,15 +870,18 @@ final class NewContactScreenComponent: Component { public class NewContactScreen: ViewControllerComponentContainer { public final class InitialData { + fileprivate let peer: EnginePeer? fileprivate let firstName: String? fileprivate let lastName: String? fileprivate let phoneNumber: String? fileprivate init( + peer: EnginePeer?, firstName: String?, lastName: String?, phoneNumber: String? ) { + self.peer = peer self.firstName = firstName self.lastName = lastName self.phoneNumber = phoneNumber @@ -860,13 +889,13 @@ public class NewContactScreen: ViewControllerComponentContainer { } private let context: AccountContext - fileprivate let completion: (TelegramMediaTodo) -> Void + fileprivate let completion: (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void private var isDismissed: Bool = false public init( context: AccountContext, initialData: InitialData, - completion: @escaping (TelegramMediaTodo) -> Void + completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void ) { self.context = context self.completion = completion @@ -876,8 +905,7 @@ public class NewContactScreen: ViewControllerComponentContainer { super.init(context: context, component: NewContactScreenComponent( context: context, - initialData: initialData, - completion: completion + initialData: initialData ), navigationBarAppearance: .none, theme: .default) self._hasGlassStyle = true @@ -899,15 +927,24 @@ public class NewContactScreen: ViewControllerComponentContainer { } public static func initialData( - firstName: String? = nil, - lastName: String? = nil, + peer: EnginePeer? = nil, phoneNumber: String? = nil ) -> InitialData { - return InitialData( - firstName: firstName, - lastName: lastName, - phoneNumber: phoneNumber - ) + if case let .user(user) = peer { + return InitialData( + peer: peer, + firstName: user.firstName, + lastName: user.lastName, + phoneNumber: user.phone ?? phoneNumber + ) + } else { + return InitialData( + peer: nil, + firstName: nil, + lastName: nil, + phoneNumber: phoneNumber + ) + } } fileprivate func complete(result: NewContactScreenComponent.Result) { @@ -921,7 +958,11 @@ public class NewContactScreen: ViewControllerComponentContainer { noteText: result.note.string, noteEntities: entities, addToPrivacyExceptions: false - ).startStandalone() + ).startStandalone(completed: { [weak self] in + if !result.syncContactToPhone { + self?.completion(result.peer, nil, nil) + } + }) } else { let _ = self.context.engine.contacts.importContact( firstName: result.firstName, @@ -931,5 +972,36 @@ public class NewContactScreen: ViewControllerComponentContainer { noteEntities: entities ).startStandalone() } + + if result.syncContactToPhone, let contactDataManager = self.context.sharedContext.contactDataManager { + let composedContactData = DeviceContactExtendedData( + basicData: DeviceContactBasicData( + firstName: result.firstName, + lastName: result.lastName, + phoneNumbers: [ + DeviceContactPhoneNumberData(label: "_$!!$_", value: result.phoneNumber) + ] + ), + middleName: "", + prefix: "", + suffix: "", + organization: "", + jobTitle: "", + department: "", + emailAddresses: [], + urls: [], + addresses: [], + birthdayDate: nil, + socialProfiles: [], + instantMessagingProfiles: [], + note: "" + ) + let _ = (contactDataManager.createContactWithData(composedContactData) + |> deliverOnMainQueue).start(next: { [weak self] contactIdAndData in + if let self, let contactIdAndData { + self.completion(result.peer, contactIdAndData.0, contactIdAndData.1) + } + }) + } } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 757ffd9a67..fe970c1f46 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -626,15 +626,21 @@ public final class GiftItemComponent: Component { let price: String switch component.subject { case let .premium(_, priceValue), let .starGift(_, priceValue): - if priceValue.contains("#") { + if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction) { buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a) - if !component.isSoldOut { - starsColor = UIColor(rgb: 0xffbe27) - } + //todo:localize + price = "Place a Bid" } else { - buttonColor = component.theme.list.itemAccentColor + if priceValue.contains("#") { + buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a) + if !component.isSoldOut { + starsColor = UIColor(rgb: 0xffbe27) + } + } else { + buttonColor = component.theme.list.itemAccentColor + } + price = priceValue } - price = priceValue case let .uniqueGift(_, priceValue): if let ribbon = component.ribbon, case let .custom(bottomValue, topValue) = ribbon.color { let topColor = UIColor(rgb: UInt32(bitPattern: topValue)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04) diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index fbd0fcdd58..44fd974edd 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -520,19 +520,28 @@ final class GiftOptionsScreenComponent: Component { isSoldOut = true } else if let _ = gift.availability { let text: String - if let perUserLimit = gift.perUserLimit { + var ribbonColor: GiftItemComponent.Ribbon.Color = .blue + if gift.flags.contains(.isAuction) { + //TODO:localize + text = "auction" + ribbonColor = .orange + outline = .orange + } else if let perUserLimit = gift.perUserLimit { text = environment.strings.Gift_Options_Gift_Limited_Left(perUserLimit.remains) } else { text = environment.strings.Gift_Options_Gift_Limited } ribbon = GiftItemComponent.Ribbon( text: text, - color: .blue + color: ribbonColor ) } if !isSoldOut && gift.flags.contains(.requiresPremium) { let text: String - if component.context.isPremium, let perUserLimit = gift.perUserLimit { + if gift.flags.contains(.isAuction) { + //TODO:localize + text = "auction" + } else if component.context.isPremium, let perUserLimit = gift.perUserLimit { text = environment.strings.Gift_Options_Gift_Premium_Left(perUserLimit.remains) } else { text = environment.strings.Gift_Options_Gift_Premium diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD index 15fee5ce98..cce609e7d9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD @@ -49,6 +49,8 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/ConfettiEffect", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 1606a4ee30..a75f4ba110 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -233,7 +233,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { case let .premium(months, amount, currency): media = [ TelegramMediaAction( - action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: nil, cryptoAmount: nil, text: item.text, entities: item.entities) + action: .giftPremium(currency: currency, amount: amount, days: months * 30, cryptoCurrency: nil, cryptoAmount: nil, text: item.text, entities: item.entities) ) ] case let .starGift(gift): diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 6c14a4a8fd..face2d1edb 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -37,6 +37,7 @@ import GiftViewScreen import UndoUI import ConfettiEffect import EdgeEffect +import AnimatedTextComponent final class GiftSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -83,6 +84,7 @@ final class GiftSetupScreenComponent: Component { private let navigationTitle = ComponentView() private let remainingCount = ComponentView() + private let auctionFooter = ComponentView() private let resaleSection = ComponentView() private let introContent = ComponentView() private let introSection = ComponentView() @@ -133,6 +135,11 @@ final class GiftSetupScreenComponent: Component { private var peerMap: [EnginePeer.Id: EnginePeer] = [:] private var sendPaidMessageStars: StarsAmount? + private var giftAuction: GiftAuctionContext? + private var giftAuctionState: GiftAuctionContext.State? + private var giftAuctionDisposable: Disposable? + private var giftAuctionTimer: SwiftSignalKit.Timer? + private var starImage: (UIImage, PresentationTheme)? private var updateDisposable: Disposable? @@ -183,6 +190,8 @@ final class GiftSetupScreenComponent: Component { self.inputMediaNodeDataDisposable?.dispose() self.updateDisposable?.dispose() self.optionsDisposable?.dispose() + self.giftAuctionDisposable?.dispose() + self.giftAuctionTimer?.invalidate() } func scrollToTop() { @@ -230,11 +239,37 @@ final class GiftSetupScreenComponent: Component { self.buttonSeparator.opacity = Float(bottomPanelAlpha) } + private var openedAuction: Bool = false + + @objc private func proceed() { guard let component = self.component, let environment = self.environment else { return } + if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction), let navigationController = environment.controller()?.navigationController as? NavigationController { + + let giftAuction = self.giftAuction + let openAuction = { [weak giftAuction, weak navigationController] in + guard let giftAuction, let navigationController else { + return + } + let controller = component.context.sharedContext.makeGiftAuctionScreen(context: component.context, gift: .generic(gift), auctionContext: giftAuction) + navigationController.pushViewController(controller) + } + + if self.openedAuction { + openAuction() + } else { + self.openedAuction = true + let controller = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, gift: .generic(gift), completion: { + openAuction() + }) + environment.controller()?.push(controller) + } + return + } + switch component.subject { case let .premium(product): if self.payWithStars, let starsPrice = product.starsPrice, let peer = self.peerMap[component.peerId] { @@ -618,6 +653,24 @@ final class GiftSetupScreenComponent: Component { self.hideName = true } + if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction) { + let giftAuction = GiftAuctionContext(account: component.context.account, giftId: gift.id) + self.giftAuction = giftAuction + self.giftAuctionDisposable = (giftAuction.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.giftAuctionState = state + self.state?.updated() + }) + + self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.state?.updated() + }, queue: Queue.mainQueue()) + self.giftAuctionTimer?.start() + } + var releasedBy: EnginePeer.Id? if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil { self.includeUpgrade = true @@ -830,45 +883,7 @@ final class GiftSetupScreenComponent: Component { contentHeight += environment.navigationHeight contentHeight += 26.0 - - if case let .starGift(starGift, _) = component.subject, let availability = starGift.availability { - let remains: Int32 = availability.remains - let total: Int32 = availability.total - let position = CGFloat(remains) / CGFloat(total) - let sold = total - remains - let remainingCountSize = self.remainingCount.update( - transition: transition, - component: AnyComponent(RemainingCountComponent( - inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), - activeColors: [UIColor(rgb: 0x5bc2ff), UIColor(rgb: 0x2d9eff)], - inactiveTitle: environment.strings.Gift_Send_Remains(remains), - inactiveValue: "", - inactiveTitleColor: environment.theme.list.itemSecondaryTextColor, - activeTitle: "", - activeValue: environment.strings.Gift_Send_Sold(sold),//totalString, - activeTitleColor: .white, - badgeText: "", - badgePosition: position, - badgeGraphPosition: position, - invertProgress: true, - leftString: "", - groupingSeparator: environment.dateTimeFormat.groupingSeparator - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) - ) - let remainingCountFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight - 77.0), size: remainingCountSize) - if let remainingCountView = self.remainingCount.view { - if remainingCountView.superview == nil { - self.scrollView.addSubview(remainingCountView) - } - transition.setFrame(view: remainingCountView, frame: remainingCountFrame) - } - contentHeight += remainingCountSize.height - contentHeight -= 77.0 - contentHeight += sectionSpacing - } - + if case let .starGift(starGift, forceUnique) = component.subject, let availability = starGift.availability, availability.resale > 0 { if let forceUnique, !forceUnique { } else { @@ -1381,8 +1396,96 @@ final class GiftSetupScreenComponent: Component { } contentHeight += hideSectionSize.height } + contentHeight += sectionSpacing + + if case let .starGift(starGift, _) = component.subject, let availability = starGift.availability { + contentHeight -= 77.0 + contentHeight += 16.0 - contentHeight += 24.0 + let remains: Int32 = availability.remains + let total: Int32 = availability.total + let position = CGFloat(remains) / CGFloat(total) + let sold = total - remains + let remainingCountSize = self.remainingCount.update( + transition: transition, + component: AnyComponent(RemainingCountComponent( + inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), + activeColors: [UIColor(rgb: 0x5bc2ff), UIColor(rgb: 0x2d9eff)], + inactiveTitle: environment.strings.Gift_Send_Remains(remains), + inactiveValue: "", + inactiveTitleColor: environment.theme.list.itemSecondaryTextColor, + activeTitle: "", + activeValue: environment.strings.Gift_Send_Sold(sold),//totalString, + activeTitleColor: .white, + badgeText: "", + badgePosition: position, + badgeGraphPosition: position, + invertProgress: true, + leftString: "", + groupingSeparator: environment.dateTimeFormat.groupingSeparator + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let remainingCountFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: remainingCountSize) + if let remainingCountView = self.remainingCount.view { + if remainingCountView.superview == nil { + self.scrollView.addSubview(remainingCountView) + } + transition.setFrame(view: remainingCountView, frame: remainingCountFrame) + } + contentHeight += remainingCountSize.height + contentHeight += 7.0 + + if starGift.flags.contains(.isAuction) { + let parsedString = parseMarkdownIntoAttributedString("50 gifts are dropped at varying intervals to the top 50 bidders by bid amount. [Learn more >]()", attributes: footerAttributes) + let auctionFooterText = NSMutableAttributedString(attributedString: parsedString) + + if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme { + self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme) + } + if let range = auctionFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 { + auctionFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: auctionFooterText.string)) + } + + let auctionFooterSize = self.auctionFooter.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(auctionFooterText), + maximumNumberOfLines: 0, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.1), + highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component, case let .starGift(gift, _) = component.subject, let controller = self.environment?.controller() else { + return + } + let infoController = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, gift: .generic(gift), completion: nil) + controller.push(infoController) + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 10000.0) + ) + let auctionFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: auctionFooterSize) + if let auctionFooterView = self.auctionFooter.view { + if auctionFooterView.superview == nil { + self.scrollView.addSubview(auctionFooterView) + } + transition.setFrame(view: auctionFooterView, frame: auctionFooterFrame) + } + contentHeight += remainingCountSize.height + } + + contentHeight += sectionSpacing + } + let buttonHeight: CGFloat = 50.0 let bottomPanelPadding: CGFloat = 12.0 @@ -1439,6 +1542,8 @@ final class GiftSetupScreenComponent: Component { } } + var buttonTitleItems: [AnyComponentWithIdentity] = [] + let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.starImage?.0 { buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) @@ -1447,9 +1552,71 @@ final class GiftSetupScreenComponent: Component { buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) } + var buttonIsLoading = false + if let _ = self.giftAuction { + //TODO:localize + let buttonAttributedString = NSMutableAttributedString(string: "Place a Bid", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + buttonTitleItems.append(AnyComponentWithIdentity(id: "bid", component: AnyComponent( + MultilineTextComponent(text: .plain(buttonAttributedString)) + ))) + if let giftAuctionState = self.giftAuctionState { + switch giftAuctionState.auctionState { + case let .ongoing(_, _, _, _, _, nextDropDate, _, _): + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let dropTimeout = nextDropDate - currentTime + + let minutes = Int(dropTimeout / 60) + let seconds = Int(dropTimeout % 60) + + let rawString = environment.strings.Gift_Setup_NextDropIn + var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = [] + var startIndex = rawString.startIndex + while true { + if let range = rawString.range(of: "{", range: startIndex ..< rawString.endIndex) { + if range.lowerBound != startIndex { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< range.lowerBound])))) + } + + startIndex = range.upperBound + if let endRange = rawString.range(of: "}", range: startIndex ..< rawString.endIndex) { + let controlString = rawString[range.upperBound ..< endRange.lowerBound] + if controlString == "m" { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(minutes, minDigits: 2))) + } else if controlString == "s" { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(seconds, minDigits: 2))) + } + + startIndex = endRange.upperBound + } + } else { + break + } + } + if startIndex != rawString.endIndex { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< rawString.endIndex])))) + } + + buttonTitleItems.append(AnyComponentWithIdentity(id: "timer", component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 12.0, weight: .medium, traits: .monospacedNumbers), + color: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), + items: buttonAnimatedTitleItems, + noDelay: true + )))) + case .finished: + buttonIsEnabled = false + } + } else { + buttonIsLoading = true + } + } else { + buttonTitleItems.append(AnyComponentWithIdentity(id: buttonString, component: AnyComponent( + MultilineTextComponent(text: .plain(buttonAttributedString)) + ))) + } + let buttonSideInset = environment.safeInsets.left + 36.0 let buttonSize = self.button.update( - transition: transition, + transition: .spring(duration: 0.2), component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, @@ -1459,11 +1626,11 @@ final class GiftSetupScreenComponent: Component { isShimmering: true ), content: AnyComponentWithIdentity( - id: AnyHashable(buttonString), - component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + id: AnyHashable("title"), + component: AnyComponent(VStack(buttonTitleItems, spacing: 1.0)) ), isEnabled: buttonIsEnabled, - displaysProgress: self.inProgress, + displaysProgress: buttonIsLoading || self.inProgress, action: { [weak self] in self?.proceed() } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/RemainingCountComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/RemainingCountComponent.swift index 82b60e5362..51005703d2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/RemainingCountComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/RemainingCountComponent.swift @@ -12,6 +12,7 @@ import MultilineTextComponent import Markdown import TextFormat import RoundedRectWithTailPath +import GlassBackgroundComponent public class RemainingCountComponent: Component { private let inactiveColor: UIColor @@ -125,22 +126,12 @@ public class RemainingCountComponent: Component { private let activeTitleLabel = ComponentView() private let activeValueLabel = ComponentView() - private let badgeView: UIView - private let badgeMaskView: UIView - private let badgeShapeLayer = CAShapeLayer() - - private let badgeForeground: SimpleLayer - private var badgeLabel: BadgeLabelView? - private let badgeLeftLabel = ComponentView() - private let badgeLabelMaskView = UIImageView() - - private var badgeTailPosition: CGFloat = 0.0 - private var badgeShapeArguments: (Double, Double, CGSize, CGFloat, CGFloat)? - + private let activeChromeView = UIImageView() + override init(frame: CGRect) { self.container = UIView() self.container.clipsToBounds = true - self.container.layer.cornerRadius = 9.0 + self.container.layer.cornerRadius = 15.0 self.inactiveBackground = SimpleLayer() @@ -149,18 +140,6 @@ public class RemainingCountComponent: Component { self.activeBackground = SimpleLayer() self.activeBackground.anchorPoint = CGPoint() - - self.badgeView = UIView() - self.badgeView.alpha = 0.0 - - self.badgeShapeLayer.fillColor = UIColor.white.cgColor - self.badgeShapeLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) - - self.badgeMaskView = UIView() - self.badgeMaskView.layer.addSublayer(self.badgeShapeLayer) - self.badgeView.mask = self.badgeMaskView - - self.badgeForeground = SimpleLayer() super.init(frame: frame) @@ -168,27 +147,11 @@ public class RemainingCountComponent: Component { self.container.layer.addSublayer(self.inactiveBackground) self.container.addSubview(self.activeContainer) self.activeContainer.layer.addSublayer(self.activeBackground) + self.activeContainer.addSubview(self.activeChromeView) - //self.addSubview(self.badgeView) - self.badgeView.layer.addSublayer(self.badgeForeground) - //self.badgeView.addSubview(self.badgeLabel) - - self.badgeLabelMaskView.contentMode = .scaleToFill - self.badgeLabelMaskView.image = generateImage(CGSize(width: 2.0, height: 30.0), rotatedContext: { size, context in - let bounds = CGRect(origin: .zero, size: size) - context.clear(bounds) - - let colorsArray: [CGColor] = [ - UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, - UIColor(rgb: 0xffffff).cgColor, - UIColor(rgb: 0xffffff).cgColor, - UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, - ] - var locations: [CGFloat] = [0.0, 0.24, 0.76, 1.0] - let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) - }) + self.activeChromeView.layer.compositingFilter = "overlayBlendMode" + self.activeChromeView.alpha = 0.8 + self.activeChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 30.0, height: 30.0), isDark: false, fillColor: .clear) self.isUserInteractionEnabled = false } @@ -197,83 +160,6 @@ public class RemainingCountComponent: Component { fatalError("init(coder:) has not been implemented") } - deinit { - self.badgeShapeAnimator?.invalidate() - } - - private var didPlayAppearanceAnimation = false - func playAppearanceAnimation(component: RemainingCountComponent, badgeFullSize: CGSize, from: CGFloat? = nil) { - if from == nil { - self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - } - - let rotationAngle: CGFloat - if badgeFullSize.width > 100.0 { - rotationAngle = 0.2 - } else { - rotationAngle = 0.26 - } - - let to: CGFloat = self.badgeView.center.x - - let positionAnimation = CABasicAnimation(keyPath: "position.x") - positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: from ?? self.container.frame.width, y: 0.0)) - positionAnimation.toValue = NSValue(cgPoint: CGPoint(x: to, y: 0.0)) - positionAnimation.duration = 0.5 - positionAnimation.fillMode = .forwards - positionAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - self.badgeView.layer.add(positionAnimation, forKey: "appearance1") - - if from != to { - let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - rotateAnimation.fromValue = 0.0 as NSNumber - rotateAnimation.toValue = rotationAngle as NSNumber - rotateAnimation.duration = 0.15 - rotateAnimation.fillMode = .forwards - rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) - rotateAnimation.isRemovedOnCompletion = false - self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") - - Queue.mainQueue().after(0.5, { - let bounceAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - bounceAnimation.fromValue = rotationAngle as NSNumber - bounceAnimation.toValue = -0.04 as NSNumber - bounceAnimation.duration = 0.2 - bounceAnimation.fillMode = .forwards - bounceAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) - bounceAnimation.isRemovedOnCompletion = false - self.badgeView.layer.add(bounceAnimation, forKey: "appearance3") - self.badgeView.layer.removeAnimation(forKey: "appearance2") - - Queue.mainQueue().after(0.2) { - let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - returnAnimation.fromValue = -0.04 as NSNumber - returnAnimation.toValue = 0.0 as NSNumber - returnAnimation.duration = 0.15 - returnAnimation.fillMode = .forwards - returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) - self.badgeView.layer.add(returnAnimation, forKey: "appearance4") - self.badgeView.layer.removeAnimation(forKey: "appearance3") - } - }) - } - - if from == nil { - self.badgeView.alpha = 1.0 - self.badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) - } - - if let badgeText = component.badgeText, let badgeLabel = self.badgeLabel { - let transition: ComponentTransition = .easeInOut(duration: from != nil ? 0.3 : 0.5) - var frameTransition = transition - if from == nil { - frameTransition = frameTransition.withAnimation(.none) - } - let badgeLabelSize = badgeLabel.update(value: badgeText, transition: transition) - frameTransition.setFrame(view: badgeLabel, frame: CGRect(origin: CGPoint(x: 10.0, y: -2.0), size: badgeLabelSize)) - } - } - var previousAvailableSize: CGSize? func update(component: RemainingCountComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.component = component @@ -282,17 +168,6 @@ public class RemainingCountComponent: Component { let size = CGSize(width: availableSize.width, height: 90.0) - - if self.badgeLabel == nil { - let badgeLabel = BadgeLabelView(groupingSeparator: component.groupingSeparator) - let _ = badgeLabel.update(value: "0", transition: .immediate) - badgeLabel.mask = self.badgeLabelMaskView - self.badgeLabel = badgeLabel - self.badgeView.addSubview(badgeLabel) - } - - self.badgeLabel?.color = component.activeTitleColor - let lineHeight: CGFloat = 30.0 let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - lineHeight), size: CGSize(width: size.width, height: lineHeight)) self.container.frame = containerFrame @@ -468,129 +343,14 @@ public class RemainingCountComponent: Component { progressTransition.setFrame(view: self.activeContainer, frame: CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: activeWidth, height: lineHeight))) progressTransition.setFrame(layer: self.activeBackground, frame: CGRect(origin: CGPoint(x: -activityPosition, y: 0.0), size: CGSize(width: containerFrame.width * 1.35, height: lineHeight))) } + + progressTransition.setFrame(view: self.activeChromeView, frame: CGRect(origin: CGPoint(x: -activityPosition, y: 0.0), size: CGSize(width: activeWidth, height: lineHeight))) + if self.activeBackground.animation(forKey: "movement") == nil { self.activeBackground.position = CGPoint(x: -self.activeContainer.frame.width * 0.35, y: lineHeight / 2.0) } } - let countWidth: CGFloat - if let badgeText = component.badgeText { - countWidth = getLabelWidth(badgeText) - } else { - countWidth = 51.0 - } - - let badgeSpacing: CGFloat = 4.0 - - let badgeLeftSize = self.badgeLeftLabel.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.leftString, - font: Font.semibold(15.0), - textColor: component.activeTitleColor - ) - ) - ) - ), - environment: {}, - containerSize: availableSize - ) - if let view = self.badgeLeftLabel.view { - if view.superview == nil { - self.badgeView.addSubview(view) - } - view.frame = CGRect(origin: CGPoint(x: 10.0 + countWidth + badgeSpacing, y: 4.0 + UIScreenPixel), size: badgeLeftSize) - } - - let badgeWidth: CGFloat = countWidth + 20.0 + badgeSpacing + badgeLeftSize.width - let badgeSize = CGSize(width: badgeWidth, height: 30.0) - let badgeFullSize = CGSize(width: badgeWidth, height: badgeSize.height + 8.0) - let tailSize = CGSize(width: 15.0, height: 6.0) - let tailRadius: CGFloat = 3.0 - self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeFullSize) - self.badgeShapeLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -4.0), size: badgeFullSize) - - self.badgeView.bounds = CGRect(origin: .zero, size: badgeFullSize) - - let currentBadgeX = self.badgeView.center.x - - let badgePosition = component.badgePosition - - if badgePosition > 1.0 - 0.15 { - progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0)) - - if progressTransition.animation.isImmediate { - self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailSize: tailSize, tailRadius: tailRadius, tailPosition: 1.0).cgPath - } else { - self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 1.0) - self.animateBadgeTailPositionChange() - } - self.badgeTailPosition = 1.0 - - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - } else { - self.badgeView.center = CGPoint(x: 3.0 + (size.width - 6.0) * badgePosition + 3.0, y: 56.0) - } - } else if badgePosition < 0.15 { - progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0)) - - if progressTransition.animation.isImmediate { - self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailSize: tailSize, tailRadius: tailRadius, tailPosition: 0.0).cgPath - } else { - self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 0.0) - self.animateBadgeTailPositionChange() - } - self.badgeTailPosition = 0.0 - - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - - } else { - self.badgeView.center = CGPoint(x: (size.width - 6.0) * badgePosition, y: 56.0) - } - } else { - progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0)) - - if progressTransition.animation.isImmediate { - self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailSize: tailSize, tailRadius: tailRadius, tailPosition: 0.5).cgPath - } else { - self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 0.5) - self.animateBadgeTailPositionChange() - } - self.badgeTailPosition = 0.5 - - if let _ = self.badgeView.layer.animation(forKey: "appearance1") { - - } else { - self.badgeView.center = CGPoint(x: size.width * badgePosition, y: 56.0) - } - } - self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeFullSize.width * 3.0, height: badgeFullSize.height)) - if self.badgeForeground.animation(forKey: "movement") == nil { - self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0 - self.badgeForeground.frame.width * 0.35, y: badgeFullSize.height / 2.0) - } - - self.badgeLabelMaskView.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 36.0) - - if !self.didPlayAppearanceAnimation || !transition.animation.isImmediate { - self.didPlayAppearanceAnimation = true - if transition.animation.isImmediate { - if component.badgePosition < 0.1 { - self.badgeView.alpha = 1.0 - if let badgeText = component.badgeText, let badgeLabel = self.badgeLabel { - let badgeLabelSize = badgeLabel.update(value: badgeText, transition: .immediate) - transition.setFrame(view: badgeLabel, frame: CGRect(origin: CGPoint(x: 10.0, y: -2.0), size: badgeLabelSize)) - } - } else { - self.playAppearanceAnimation(component: component, badgeFullSize: badgeFullSize) - } - } else { - self.playAppearanceAnimation(component: component, badgeFullSize: badgeFullSize, from: currentBadgeX) - } - } - if self.previousAvailableSize != availableSize { self.previousAvailableSize = availableSize @@ -600,9 +360,7 @@ public class RemainingCountComponent: Component { locations.append(delta * CGFloat(i)) } - let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: component.activeColors, locations: locations, direction: .horizontal) - self.badgeForeground.contentsGravity = .resizeAspectFill - self.badgeForeground.contents = gradient?.cgImage + let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: Array(component.activeColors.reversed()), locations: locations, direction: .horizontal) self.activeBackground.contentsGravity = .resizeAspectFill self.activeBackground.contents = gradient?.cgImage @@ -613,54 +371,14 @@ public class RemainingCountComponent: Component { return size } - private var badgeShapeAnimator: ConstantDisplayLinkAnimator? - private func animateBadgeTailPositionChange() { - if self.badgeShapeAnimator == nil { - self.badgeShapeAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in - self?.animateBadgeTailPositionChange() - }) - self.badgeShapeAnimator?.isPaused = true - } - - if let (startTime, duration, badgeSize, initial, target) = self.badgeShapeArguments { - self.badgeShapeAnimator?.isPaused = false - - let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration))) - let value = initial + (target - initial) * t - - let tailSize = CGSize(width: 15.0, height: 6.0) - let tailRadius: CGFloat = 3.0 - self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailSize: tailSize, tailRadius: tailRadius, tailPosition: value).cgPath - - if t >= 1.0 { - self.badgeShapeArguments = nil - self.badgeShapeAnimator?.isPaused = true - self.badgeShapeAnimator?.invalidate() - self.badgeShapeAnimator = nil - } - } else { - self.badgeShapeAnimator?.isPaused = true - self.badgeShapeAnimator?.invalidate() - self.badgeShapeAnimator = nil - } - } - private func setupGradientAnimations() { guard let _ = self.component else { return } - if let _ = self.badgeForeground.animation(forKey: "movement") { + if let _ = self.activeBackground.animation(forKey: "movement") { } else { CATransaction.begin() - let badgeOffset = (self.badgeForeground.frame.width - self.badgeView.bounds.width) / 2.0 - let badgePreviousValue = self.badgeForeground.position.x - var badgeNewValue: CGFloat = badgeOffset - if badgeOffset - badgePreviousValue < self.badgeForeground.frame.width * 0.25 { - badgeNewValue -= self.badgeForeground.frame.width * 0.35 - } - self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0) - let lineOffset = 0.0 let linePreviousValue = self.activeBackground.position.x var lineNewValue: CGFloat = lineOffset @@ -671,18 +389,7 @@ public class RemainingCountComponent: Component { } lineNewValue -= self.activeContainer.frame.minX self.activeBackground.position = CGPoint(x: lineNewValue, y: 0.0) - - let badgeAnimation = CABasicAnimation(keyPath: "position.x") - badgeAnimation.duration = 4.5 - badgeAnimation.fromValue = badgePreviousValue - badgeAnimation.toValue = badgeNewValue - badgeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - - CATransaction.setCompletionBlock { [weak self] in - self?.setupGradientAnimations() - } - self.badgeForeground.add(badgeAnimation, forKey: "movement") - + let lineAnimation = CABasicAnimation(keyPath: "position.x") lineAnimation.duration = 4.5 lineAnimation.fromValue = linePreviousValue @@ -703,181 +410,3 @@ public class RemainingCountComponent: Component { return view.update(component: self, availableSize: availableSize, transition: transition) } } - - -private let spaceWidth: CGFloat = 3.0 -private let labelWidth: CGFloat = 10.0 -private let labelHeight: CGFloat = 30.0 -private let labelSize = CGSize(width: labelWidth, height: labelHeight) -private let font = Font.with(size: 15.0, design: .regular, weight: .semibold, traits: []) - -final class BadgeLabelView: UIView { - private class StackView: UIView { - var labels: [UILabel] = [] - - var currentValue: Int32? - - var color: UIColor = .white { - didSet { - for view in self.labels { - view.textColor = self.color - } - } - } - - init(groupingSeparator: String) { - super.init(frame: CGRect(origin: .zero, size: labelSize)) - - var height: CGFloat = -labelHeight * 2.0 - for i in -2 ..< 10 { - let label = UILabel() - let itemWidth: CGFloat - if i == -2 { - label.text = groupingSeparator - itemWidth = spaceWidth - } else if i == -1 { - label.text = "9" - itemWidth = labelWidth - } else { - label.text = "\(i)" - itemWidth = labelWidth - } - label.textColor = self.color - label.font = font - label.textAlignment = .center - label.frame = CGRect(x: 0, y: height, width: itemWidth, height: labelHeight) - self.addSubview(label) - self.labels.append(label) - - height += labelHeight - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(value: Int32?, isFirst: Bool, isLast: Bool, transition: ComponentTransition) { - let previousValue = self.currentValue - self.currentValue = value - - self.labels[2].alpha = isFirst && !isLast ? 0.0 : 1.0 - - if let value { - if previousValue == 9 && value < 9 { - self.bounds = CGRect( - origin: CGPoint( - x: 0.0, - y: -1.0 * labelSize.height - ), - size: labelSize - ) - } - - let bounds = CGRect( - origin: CGPoint( - x: 0.0, - y: CGFloat(value) * labelSize.height - ), - size: labelSize - ) - transition.setBounds(view: self, bounds: bounds) - } else { - self.bounds = CGRect( - origin: CGPoint( - x: 0.0, - y: -2.0 * labelSize.height - ), - size: labelSize - ) - } - } - } - - private let groupingSeparator: String - private var itemViews: [Int: StackView] = [:] - - init(groupingSeparator: String) { - self.groupingSeparator = groupingSeparator - - super.init(frame: .zero) - - self.clipsToBounds = true - self.isUserInteractionEnabled = false - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var color: UIColor = .white { - didSet { - for (_, view) in self.itemViews { - view.color = self.color - } - } - } - - func update(value: String, transition: ComponentTransition) -> CGSize { - let string = value - let stringArray = Array(string.map { String($0) }.reversed()) - - let totalWidth: CGFloat = getLabelWidth(value) - - var rightX: CGFloat = totalWidth - var validIds: [Int] = [] - for i in 0 ..< stringArray.count { - validIds.append(i) - - let itemView: StackView - var itemTransition = transition - if let current = self.itemViews[i] { - itemView = current - } else { - itemTransition = transition.withAnimation(.none) - itemView = StackView(groupingSeparator: self.groupingSeparator) - itemView.color = self.color - self.itemViews[i] = itemView - self.addSubview(itemView) - } - - let digit = Int32(stringArray[i]) - itemView.update(value: digit, isFirst: i == stringArray.count - 1, isLast: i == 0, transition: transition) - - let itemWidth: CGFloat = digit != nil ? labelWidth : spaceWidth - rightX -= itemWidth - - itemTransition.setFrame( - view: itemView, - frame: CGRect(x: rightX, y: 0.0, width: labelWidth, height: labelHeight) - ) - } - - var removeIds: [Int] = [] - for (id, itemView) in self.itemViews { - if !validIds.contains(id) { - removeIds.append(id) - - transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in - itemView.removeFromSuperview() - }) - } - } - for id in removeIds { - self.itemViews.removeValue(forKey: id) - } - return CGSize(width: totalWidth, height: labelHeight) - } -} - -private func getLabelWidth(_ string: String) -> CGFloat { - var totalWidth: CGFloat = 0.0 - for c in string { - if CharacterSet.decimalDigits.contains(c.unicodeScalars[c.unicodeScalars.startIndex]) { - totalWidth += labelWidth - } else { - totalWidth += spaceWidth - } - } - return totalWidth -} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index f444c05ad3..cc7a5ca935 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -56,6 +56,10 @@ swift_library( "//submodules/TelegramUI/Components/ChatThemeScreen", "//submodules/ImageBlur", "//submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/Stories/LiveChat/StoryLiveChatMessageComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/BotPaymentsUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift new file mode 100644 index 0000000000..6e24d18396 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift @@ -0,0 +1,166 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +private let labelWidth: CGFloat = 16.0 +private let labelHeight: CGFloat = 36.0 +private let labelSize = CGSize(width: labelWidth, height: labelHeight) +private let font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: []) + +final class BadgeLabelView: UIView { + private class StackView: UIView { + var labels: [UILabel] = [] + + var currentValue: Int32 = 0 + + var color: UIColor = .white { + didSet { + for view in self.labels { + view.textColor = self.color + } + } + } + + init() { + super.init(frame: CGRect(origin: .zero, size: labelSize)) + + var height: CGFloat = -labelHeight + for i in -1 ..< 10 { + let label = UILabel() + if i == -1 { + label.text = "9" + } else { + label.text = "\(i)" + } + label.textColor = self.color + label.font = font + label.textAlignment = .center + label.frame = CGRect(x: 0, y: height, width: labelWidth, height: labelHeight) + self.addSubview(label) + self.labels.append(label) + + height += labelHeight + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(value: Int32, isFirst: Bool, isLast: Bool, transition: ComponentTransition) { + let previousValue = self.currentValue + self.currentValue = value + + self.labels[1].alpha = isFirst && !isLast ? 0.0 : 1.0 + + if previousValue == 9 && value < 9 { + self.bounds = CGRect( + origin: CGPoint( + x: 0.0, + y: -1.0 * labelSize.height + ), + size: labelSize + ) + } + + let bounds = CGRect( + origin: CGPoint( + x: 0.0, + y: CGFloat(value) * labelSize.height + ), + size: labelSize + ) + transition.setBounds(view: self, bounds: bounds) + } + } + + private var itemViews: [Int: StackView] = [:] + private var staticLabel = UILabel() + + init() { + super.init(frame: .zero) + + self.clipsToBounds = true + self.isUserInteractionEnabled = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var color: UIColor = .white { + didSet { + self.staticLabel.textColor = self.color + for (_, view) in self.itemViews { + view.color = self.color + } + } + } + + func update(value: String, transition: ComponentTransition) -> CGSize { + if value.contains(" ") { + for (_, view) in self.itemViews { + view.isHidden = true + } + + if self.staticLabel.superview == nil { + self.staticLabel.textColor = self.color + self.staticLabel.font = font + + self.addSubview(self.staticLabel) + } + + self.staticLabel.text = value + let size = self.staticLabel.sizeThatFits(CGSize(width: 100.0, height: 100.0)) + self.staticLabel.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: labelHeight)) + + return CGSize(width: ceil(self.staticLabel.bounds.width), height: ceil(self.staticLabel.bounds.height)) + } + + let string = value + let stringArray = Array(string.map { String($0) }.reversed()) + + let totalWidth = CGFloat(stringArray.count) * labelWidth + + var validIds: [Int] = [] + for i in 0 ..< stringArray.count { + validIds.append(i) + + let itemView: StackView + var itemTransition = transition + if let current = self.itemViews[i] { + itemView = current + } else { + itemTransition = transition.withAnimation(.none) + itemView = StackView() + itemView.color = self.color + self.itemViews[i] = itemView + self.addSubview(itemView) + } + + let digit = Int32(stringArray[i]) ?? 0 + itemView.update(value: digit, isFirst: i == stringArray.count - 1, isLast: i == 0, transition: transition) + + itemTransition.setFrame( + view: itemView, + frame: CGRect(x: totalWidth - labelWidth * CGFloat(i + 1), y: 0.0, width: labelWidth, height: labelHeight) + ) + } + + var removeIds: [Int] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removeIds.append(id) + + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + for id in removeIds { + self.itemViews.removeValue(forKey: id) + } + return CGSize(width: totalWidth, height: labelHeight) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift new file mode 100644 index 0000000000..bf1b49ef15 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift @@ -0,0 +1,646 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BalancedTextComponent +import BundleIconComponent +import Markdown +import TextFormat +import TelegramStringFormatting +import GiftAnimationComponent +import GlassBarButtonComponent +import ButtonComponent +import LottieComponent + +private final class GiftAuctionInfoSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let gift: StarGift + let animateOut: ActionSlot> + let getController: () -> ViewController? + + init( + context: AccountContext, + gift: StarGift, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.context = context + self.gift = gift + self.animateOut = animateOut + self.getController = getController + } + + static func ==(lhs: GiftAuctionInfoSheetContent, rhs: GiftAuctionInfoSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.gift != rhs.gift { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + private let animateOut: ActionSlot> + private let getController: () -> ViewController? + + init( + context: AccountContext, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.context = context + self.animateOut = animateOut + self.getController = getController + + super.init() + } + + func dismiss(animated: Bool) { + guard let controller = self.getController() as? GiftAuctionInfoScreen else { + return + } + if animated { + self.animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } else { + controller.dismiss(animated: false) + } + } + } + + func makeState() -> State { + return State(context: self.context, animateOut: self.animateOut, getController: self.getController) + } + + static var body: Body { + let closeButton = Child(GlassBarButtonComponent.self) + let animation = Child(GiftCompositionComponent.self) + let title = Child(BalancedTextComponent.self) + let text = Child(BalancedTextComponent.self) + let list = Child(List.self) + let button = Child(ButtonComponent.self) + + let giftCompositionExternalState = GiftCompositionComponent.ExternalState() + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let component = context.component + let state = context.state + let controller = environment.controller + + let theme = environment.theme + let strings = environment.strings + let _ = strings + + let sideInset: CGFloat = 30.0 + environment.safeInsets.left + let textSideInset: CGFloat = 30.0 + environment.safeInsets.left + + let titleFont = Font.semibold(20.0) + let textFont = Font.regular(15.0) + + let textColor = theme.actionSheet.primaryTextColor + let secondaryTextColor = theme.actionSheet.secondaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + + let spacing: CGFloat = 16.0 + var contentSize = CGSize(width: context.availableSize.width, height: 30.0) + + var animationFile: TelegramMediaFile? + switch component.gift { + case let .generic(gift): + animationFile = gift.file + default: + animationFile = nil + } + + let headerHeight: CGFloat = 210.0 + let headerSubject: GiftCompositionComponent.Subject? + if let animationFile { + headerSubject = .generic(animationFile) + } else { + headerSubject = nil + } + + if let headerSubject { + let animation = animation.update( + component: GiftCompositionComponent( + context: component.context, + theme: theme, + subject: headerSubject, + animationOffset: nil, + animationScale: nil, + displayAnimationStars: false, + externalState: giftCompositionExternalState, + requestUpdate: { [weak state] _ in + state?.updated() + } + ), + availableSize: CGSize(width: context.availableSize.width, height: headerHeight), + transition: context.transition + ) + context.add(animation + .position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight / 2.0)) + ) + } + contentSize.height += headerHeight - 78.0 + + let title = title.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: "Auction", font: titleFont, textColor: textColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + contentSize.height += spacing - 14.0 + + let text = text.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: "Join the battle for exclusive gifts.", font: textFont, textColor: secondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height + contentSize.height += spacing + 7.0 + + + //TODO:localize + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: "top", + component: AnyComponent(ParagraphComponent( + title: "Top 50 Bidders", + titleColor: textColor, + text: "50 gifts are dropped at varying intervals to the top 50 bidders by bid amount.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/Auction/Drop", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "carryover", + component: AnyComponent(ParagraphComponent( + title: "Bid Carryover", + titleColor: textColor, + text: "If your bid leaves the top 50, it will automatically join the next drop.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/Auction/NextDrop", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "missed", + component: AnyComponent(ParagraphComponent( + title: "Missed Bidders", + titleColor: textColor, + text: "If your bid doesn't win after the final drop, your Stars will be fully refunded.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/Auction/Refund", + iconColor: linkColor + )) + ) + ) + + let list = list.update( + component: List(items), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 10000.0), + transition: context.transition + ) + context.add(list + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0)) + ) + contentSize.height += list.size.height + contentSize.height += spacing + 8.0 + + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak state] _ in + guard let state else { + return + } + state.dismiss(animated: true) + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) + ) + + + var buttonTitle: [AnyComponentWithIdentity] = [] + let playButtonAnimation = ActionSlot() + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_ok"), + color: theme.list.itemCheckColors.foregroundColor, + startingPosition: .begin, + size: CGSize(width: 28.0, height: 28.0), + playOnce: playButtonAnimation + )))) + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: "Understood", + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )))) + + let button = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 10.0, + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonTitle, spacing: 2.0)) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak state] in + guard let state else { + return + } + state.dismiss(animated: true) + + if let controller = controller() as? GiftAuctionInfoScreen { + controller.completion?() + } + } + ), + availableSize: CGSize(width: context.availableSize.width - 30.0 * 2.0, height: 52.0), + transition: .immediate + ) + context.add(button + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) + ) + contentSize.height += button.size.height + + contentSize.height += 30.0 + + return contentSize + } + } +} + +final class GiftAuctionInfoSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let gift: StarGift + + init( + context: AccountContext, + gift: StarGift + ) { + self.context = context + self.gift = gift + } + + static func ==(lhs: GiftAuctionInfoSheetComponent, rhs: GiftAuctionInfoSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.gift != rhs.gift { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + + let sheetExternalState = SheetComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(GiftAuctionInfoSheetContent( + context: context.component.context, + gift: context.component.gift, + animateOut: animateOut, + getController: controller + )), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + autoAnimateOut: false, + externalState: sheetExternalState, + animateOut: animateOut, + onPan: { + }, + willDismiss: { + } + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + if animated { + if let controller = controller() as? GiftAuctionInfoScreen { + animateOut.invoke(Action { _ in + controller.dismiss(completion: nil) + }) + } + } else { + if let controller = controller() as? GiftAuctionInfoScreen { + controller.dismiss(completion: nil) + } + } + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + var sideInset: CGFloat = 0.0 + var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight) + if case .regular = environment.metrics.widthClass { + sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0 + bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight + } + + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + + return context.availableSize + } + } +} + +public final class GiftAuctionInfoScreen: ViewControllerComponentContainer { + private let context: AccountContext + private let gift: StarGift + fileprivate let completion: (() -> Void)? + + public init( + context: AccountContext, + gift: StarGift, + completion: (() -> Void)? + ) { + self.context = context + self.gift = gift + self.completion = completion + + super.init( + context: context, + component: GiftAuctionInfoSheetComponent( + context: context, + gift: gift + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } +} + +private final class ParagraphComponent: CombinedComponent { + let title: String + let titleColor: UIColor + let text: String + let textColor: UIColor + let accentColor: UIColor + let iconName: String + let iconColor: UIColor + let action: () -> Void + + public init( + title: String, + titleColor: UIColor, + text: String, + textColor: UIColor, + accentColor: UIColor, + iconName: String, + iconColor: UIColor, + action: @escaping () -> Void = {} + ) { + self.title = title + self.titleColor = titleColor + self.text = text + self.textColor = textColor + self.accentColor = accentColor + self.iconName = iconName + self.iconColor = iconColor + self.action = action + } + + static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.titleColor != rhs.titleColor { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconColor != rhs.iconColor { + return false + } + return true + } + + static var body: Body { + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + let component = context.component + + let leftInset: CGFloat = 32.0 + let rightInset: CGFloat = 24.0 + let textSideInset: CGFloat = leftInset + 8.0 + let spacing: CGFloat = 5.0 + + let textTopInset: CGFloat = 9.0 + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: Font.semibold(15.0), + textColor: component.titleColor, + paragraphAlignment: .natural + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = component.textColor + let accentColor = component.accentColor + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: accentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: component.text, attributes: markdownAttributes), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: accentColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + component.action() + } + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), + transition: .immediate + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: component.iconColor + ), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + + context.add(title + .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) + ) + + context.add(text + .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) + ) + + context.add(icon + .position(CGPoint(x: 15.0, y: textTopInset + 18.0)) + ) + + return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0) + } + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionScreen.swift new file mode 100644 index 0000000000..1e7d7340de --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionScreen.swift @@ -0,0 +1,2950 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramPresentationData +import ChatPresentationInterfaceState +import ComponentFlow +import AccountContext +import ViewControllerComponent +import TelegramCore +import SwiftSignalKit +import Display +import MultilineTextComponent +import ButtonComponent +import PlainButtonComponent +import Markdown +import SliderComponent +import RoundedRectWithTailPath +import AvatarNode +import BundleIconComponent +import TextFormat +import CheckComponent +import ContextUI +import StarsBalanceOverlayComponent +import StoryLiveChatMessageComponent +import TelegramStringFormatting +import GlassBarButtonComponent +import AnimatedTextComponent +import BotPaymentsUI +import UndoUI + +private final class BadgeComponent: Component { + let theme: PresentationTheme + let prefix: String? + let title: String + let subtitle: String? + let color: UIColor + + init( + theme: PresentationTheme, + prefix: String?, + title: String, + subtitle: String?, + color: UIColor + ) { + self.theme = theme + self.prefix = prefix + self.title = title + self.subtitle = subtitle + self.color = color + } + + static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.prefix != rhs.prefix { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.subtitle != rhs.subtitle { + return false + } + if lhs.color != rhs.color { + return false + } + return true + } + + final class View: UIView { + private let badgeView: UIView + private let badgeMaskView: UIView + private let badgeShapeLayer = SimpleShapeLayer() + + private let badgeForeground: SimpleLayer + let badgeIcon: UIImageView + private let badgeLabel: BadgeLabelView + private let badgeLabelMaskView = UIImageView() + + private let prefix = ComponentView() + private let subtitle = ComponentView() + + private var badgeTailPosition: CGFloat = 0.0 + private var badgeShapeArguments: (Double, Double, CGSize, CGFloat, CGFloat)? + + private var component: BadgeComponent? + private var isUpdating: Bool = false + + private var previousAvailableSize: CGSize? + + override init(frame: CGRect) { + self.badgeView = UIView() + self.badgeView.alpha = 0.0 + + self.badgeShapeLayer.fillColor = UIColor.white.cgColor + self.badgeShapeLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) + + self.badgeMaskView = UIView() + self.badgeMaskView.layer.addSublayer(self.badgeShapeLayer) + self.badgeView.mask = self.badgeMaskView + + self.badgeForeground = SimpleLayer() + self.badgeForeground.anchorPoint = CGPoint() + + self.badgeIcon = UIImageView() + self.badgeIcon.contentMode = .center + + self.badgeLabel = BadgeLabelView() + let _ = self.badgeLabel.update(value: "0", transition: .immediate) + self.badgeLabel.mask = self.badgeLabelMaskView + + super.init(frame: frame) + + self.addSubview(self.badgeView) + self.badgeView.layer.addSublayer(self.badgeForeground) + self.badgeView.addSubview(self.badgeIcon) + self.badgeView.addSubview(self.badgeLabel) + + self.badgeLabelMaskView.contentMode = .scaleToFill + self.badgeLabelMaskView.image = generateImage(CGSize(width: 2.0, height: 36.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.clear(bounds) + + let colorsArray: [CGColor] = [ + UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, + UIColor(rgb: 0xffffff).cgColor, + UIColor(rgb: 0xffffff).cgColor, + UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, + ] + var locations: [CGFloat] = [0.0, 0.24, 0.76, 1.0] + let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + }) + + self.isUserInteractionEnabled = false + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.badgeView.frame.contains(point) { + return self + } else { + return nil + } + } + + func update(component: BadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if self.component == nil { + self.badgeIcon.image = UIImage(bundleImageName: "Premium/SendStarsStarSliderIcon")?.withRenderingMode(.alwaysTemplate) + } + + let previousComponent = self.component + self.component = component + self.badgeIcon.tintColor = .white + + self.badgeLabel.color = .white + + let badgeLabelSize = self.badgeLabel.update(value: component.title, transition: .easeInOut(duration: 0.12)) + let countWidth: CGFloat = badgeLabelSize.width + 3.0 + var badgeWidth: CGFloat = countWidth + 54.0 + + + var badgeOffset: CGPoint = .zero + if let prefix = component.prefix { + let prefixSize = self.prefix.update( + transition: .immediate, + component: AnyComponent(Text(text: prefix, font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), color: .white)), + environment: {}, + containerSize: availableSize + ) + if let prefixView = self.prefix.view { + if prefixView.superview == nil { + self.badgeView.addSubview(prefixView) + } + prefixView.frame = CGRect(origin: CGPoint(x: 44.0, y: 9.0 - UIScreenPixel), size: prefixSize) + prefixView.alpha = 1.0 + } + badgeWidth += prefixSize.width + badgeOffset.x += prefixSize.width - 6.0 + } else if let prefixView = self.prefix.view { + prefixView.alpha = 0.0 + } + + if let subtitle = component.subtitle { + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent(Text(text: subtitle, font: Font.regular(11.0), color: UIColor.white)), + environment: {}, + containerSize: availableSize + ) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.badgeView.addSubview(subtitleView) + } + subtitleView.frame = CGRect(origin: CGPoint(x: 44.0, y: 28.0), size: subtitleSize) + subtitleView.alpha = 1.0 + } + badgeOffset.y -= 6.0 + UIScreenPixel + } else if let subtitleView = self.subtitle.view { + subtitleView.alpha = 0.0 + } + + + + let badgeSize = CGSize(width: badgeWidth, height: 48.0) + let badgeFullSize = CGSize(width: badgeWidth, height: 48.0 + 12.0) + self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeFullSize) + self.badgeShapeLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -4.0), size: badgeFullSize) + + self.badgeView.bounds = CGRect(origin: .zero, size: badgeFullSize) + + self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 600.0, height: badgeFullSize.height + 10.0)) + + self.badgeIcon.frame = CGRect(x: 10.0, y: 9.0, width: 30.0, height: 30.0) + self.badgeLabelMaskView.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 36.0) + + self.badgeView.alpha = 1.0 + + + let size = badgeSize + transition.setFrame(view: self.badgeLabel, frame: CGRect(origin: CGPoint(x: badgeOffset.x + 14.0 + floorToScreenPixels((badgeFullSize.width - badgeLabelSize.width) / 2.0), y: 5.0 + badgeOffset.y), size: badgeLabelSize)) + + if self.previousAvailableSize != availableSize || previousComponent?.color != component.color { + self.previousAvailableSize = availableSize + + let activeColors: [UIColor] = [ + component.color, + component.color + ] + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(activeColors.count - 1) + for i in 0 ..< activeColors.count { + locations.append(delta * CGFloat(i)) + } + + let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: activeColors, locations: locations, direction: .horizontal) + self.badgeForeground.contentsGravity = .resizeAspectFill + self.badgeForeground.contents = gradient?.cgImage + + self.setupGradientAnimations() + } + + return size + } + + func adjustTail(size: CGSize, overflowWidth: CGFloat) { + var tailPosition = size.width * 0.5 + tailPosition += overflowWidth + tailPosition = max(0.0, min(size.width, tailPosition)) + + let tailPositionFraction = tailPosition / size.width + self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: size, tailPosition: tailPositionFraction).cgPath + + let transition: ContainedViewLayoutTransition = .immediate + transition.updateAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: tailPositionFraction, y: 1.0)) + transition.updatePosition(layer: self.badgeView.layer, position: CGPoint(x: (tailPositionFraction - 0.5) * size.width, y: 0.0)) + } + + func updateBadgeAngle(angle: CGFloat) { + let transition: ContainedViewLayoutTransition = .immediate + transition.updateTransformRotation(view: self.badgeView, angle: angle) + } + + private func setupGradientAnimations() { + guard let _ = self.component else { + return + } + if let _ = self.badgeForeground.animation(forKey: "movement") { + } else { + CATransaction.begin() + + let badgePreviousValue = self.badgeForeground.position.x + let badgeNewValue: CGFloat + if self.badgeForeground.position.x == -300.0 { + badgeNewValue = 0.0 + } else { + badgeNewValue = -300.0 + } + self.badgeForeground.position = CGPoint(x: badgeNewValue, y: 0.0) + + let badgeAnimation = CABasicAnimation(keyPath: "position.x") + badgeAnimation.duration = 4.5 + badgeAnimation.fromValue = badgePreviousValue + badgeAnimation.toValue = badgeNewValue + badgeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + CATransaction.setCompletionBlock { [weak self] in + self?.setupGradientAnimations() + } + self.badgeForeground.add(badgeAnimation, forKey: "movement") + + CATransaction.commit() + } + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class PeerPlaceComponent: Component { + let theme: PresentationTheme + let place: Int32 + let groupingSeparator: String + + init( + theme: PresentationTheme, + place: Int32, + groupingSeparator: String + ) { + self.theme = theme + self.place = place + self.groupingSeparator = groupingSeparator + } + + static func ==(lhs: PeerPlaceComponent, rhs: PeerPlaceComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.place != rhs.place { + return false + } + if lhs.groupingSeparator != rhs.groupingSeparator { + return false + } + return true + } + + final class View: UIView { + private var background = UIImageView() + private let label = ComponentView() + + private var component: PeerPlaceComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(self.background) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func update(component: PeerPlaceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let textColor: UIColor + let backgroundColors: [UIColor]? + if component.place < 4 { + textColor = .white + switch component.place { + case 1: + backgroundColors = [UIColor(rgb: 0xffa901), UIColor(rgb: 0xffcd3b)] + case 2: + backgroundColors = [UIColor(rgb: 0x999999), UIColor(rgb: 0xbbbbbb)] + case 3: + backgroundColors = [UIColor(rgb: 0xcb692e), UIColor(rgb: 0xdc9a59)] + default: + backgroundColors = nil + } + } else { + textColor = component.theme.list.itemSecondaryTextColor + backgroundColors = nil + } + + let backgroundSize = CGSize(width: 24.0, height: 24.0) + if let backgroundColors { + let colors: NSArray = Array(backgroundColors.map { $0.cgColor }) as NSArray + self.background.image = generateGradientFilledCircleImage( + diameter: backgroundSize.width, + colors: colors, + direction: .vertical + ) + self.background.isHidden = false + } else { + self.background.isHidden = true + } + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - backgroundSize.height) * 0.5)), size: backgroundSize) + self.background.frame = backgroundFrame + + let labelSize = self.label.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(component.place, component.groupingSeparator), font: Font.regular(17.0), textColor: textColor)))), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - labelSize.height) * 0.5)), size: labelSize) + if let labelView = self.label.view { + if labelView.superview == nil { + self.addSubview(labelView) + } + labelView.frame = labelFrame + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class PeerComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let groupingSeparator: String + let peer: EnginePeer + let place: Int32 + let amount: Int64 + let isLast: Bool + + init( + context: AccountContext, + theme: PresentationTheme, + groupingSeparator: String, + peer: EnginePeer, + place: Int32, + amount: Int64, + isLast: Bool + ) { + self.context = context + self.theme = theme + self.groupingSeparator = groupingSeparator + self.peer = peer + self.place = place + self.amount = amount + self.isLast = isLast + } + + static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.place != rhs.place { + return false + } + if lhs.amount != rhs.amount { + return false + } + if lhs.isLast != rhs.isLast { + return false + } + return true + } + + final class View: UIView { + private var avatarNode: AvatarNode? + private let place = ComponentView() + private let title = ComponentView() + private let amount = ComponentView() + private let amountStar = UIImageView() + private let separator = SimpleLayer() + + private var component: PeerComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.separator) + + self.amountStar.image = UIImage(bundleImageName: "Premium/Stars/StarSmall") + self.addSubview(self.amountStar) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func update(component: PeerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let size = CGSize(width: availableSize.width, height: 52.0) + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let badgeSize = self.place.update( + transition: .immediate, + component: AnyComponent(PeerPlaceComponent(theme: component.theme, place: component.place, groupingSeparator: presentationData.dateTimeFormat.groupingSeparator)), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let badgeFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - badgeSize.height) / 2.0)), size: badgeSize) + if let badgeView = self.place.view { + if badgeView.superview == nil { + self.addSubview(badgeView) + } + badgeView.frame = badgeFrame + } + + let avatarNode: AvatarNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 16.0)) + self.avatarNode = avatarNode + self.addSubview(avatarNode.view) + } + + let avatarSize = CGSize(width: 40.0, height: 40.0) + let avatarFrame = CGRect(origin: CGPoint(x: 51.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) + avatarNode.frame = avatarFrame + avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, synchronousLoad: true) + avatarNode.updateSize(size: avatarFrame.size) + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: avatarSize.width + 10.0 * 2.0, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: 110.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + + let amountSize = self.amount.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(Int32(clamping: component.amount), component.groupingSeparator), font: Font.regular(15.0), textColor: component.theme.list.itemSecondaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let amountFrame = CGRect(origin: CGPoint(x: availableSize.width - amountSize.width, y: floorToScreenPixels((size.height - amountSize.height) / 2.0)), size: titleSize) + if let amountView = self.amount.view { + if amountView.superview == nil { + self.addSubview(amountView) + } + amountView.frame = amountFrame + } + + if let icon = self.amountStar.image { + self.amountStar.frame = CGRect(origin: CGPoint(x: amountFrame.minX - icon.size.width, y: floorToScreenPixels((size.height - icon.size.height) / 2.0) - UIScreenPixel), size: icon.size) + } + + self.separator.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor + self.separator.frame = CGRect(origin: CGPoint(x: 110.0, y: size.height), size: CGSize(width: size.width - 110.0, height: 1.0 - UIScreenPixel)) + transition.setAlpha(layer: self.separator, alpha: component.isLast ? 0.0 : 1.0) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class SliderBackgroundComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let value: CGFloat + let topCutoff: CGFloat? + let color: UIColor + + init( + theme: PresentationTheme, + strings: PresentationStrings, + value: CGFloat, + topCutoff: CGFloat?, + color: UIColor + ) { + self.theme = theme + self.strings = strings + self.value = value + self.topCutoff = topCutoff + self.color = color + } + + static func ==(lhs: SliderBackgroundComponent, rhs: SliderBackgroundComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.value != rhs.value { + return false + } + if lhs.topCutoff != rhs.topCutoff { + return false + } + if lhs.color != rhs.color { + return false + } + return true + } + + private enum TopTextOverflowState { + case left + case center + case right + + func animates(from: TopTextOverflowState) -> Bool { + switch self { + case .left: + return false + case .center: + switch from { + case .left: + return false + case .center: + return false + case .right: + return true + } + case .right: + switch from { + case .left: + return false + case .center: + return true + case .right: + return false + } + } + } + } + + final class View: UIView { + private let sliderBackground = UIView() + private let sliderForeground = UIView() + private let sliderStars = SliderStarsView() + + private let topForegroundLine = SimpleLayer() + private let topBackgroundLine = SimpleLayer() + private let topForegroundText = ComponentView() + private let topBackgroundText = ComponentView() + + private var topTextOverflowState: TopTextOverflowState? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.sliderBackground.clipsToBounds = true + + self.sliderForeground.clipsToBounds = true + self.sliderForeground.addSubview(self.sliderStars) + + self.addSubview(self.sliderBackground) + self.addSubview(self.sliderForeground) + + self.sliderBackground.layer.addSublayer(self.topBackgroundLine) + self.sliderForeground.layer.addSublayer(self.topForegroundLine) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: SliderBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.sliderBackground.backgroundColor = component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(component.theme.overallDarkAppearance ? 0.2 : 0.07) + self.sliderForeground.backgroundColor = component.color + self.topForegroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor + self.topBackgroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor + + transition.setFrame(view: self.sliderBackground, frame: CGRect(origin: CGPoint(), size: availableSize)) + + let sliderMinWidth = availableSize.height + let sliderAreaWidth: CGFloat = availableSize.width - sliderMinWidth + let sliderForegroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * component.value), height: availableSize.height)) + transition.setFrame(view: self.sliderForeground, frame: sliderForegroundFrame) + + self.sliderBackground.layer.cornerRadius = availableSize.height * 0.5 + self.sliderForeground.layer.cornerRadius = availableSize.height * 0.5 + + self.sliderStars.frame = CGRect(origin: .zero, size: availableSize) + self.sliderStars.update(size: availableSize, value: component.value) + + self.sliderForeground.isHidden = sliderForegroundFrame.width <= sliderMinWidth + + let topCutoff = component.topCutoff ?? 0.0 + + let topX = floorToScreenPixels(sliderAreaWidth * topCutoff) + let topLineAvoidDistance = 6.0 + let knobWidth: CGFloat = 30.0 + let topLineClosestEdge = min(abs(sliderForegroundFrame.maxX - topX), abs(sliderForegroundFrame.maxX - knobWidth - topX)) + var topLineOverlayFactor = topLineClosestEdge / topLineAvoidDistance + topLineOverlayFactor = max(0.0, min(1.0, topLineOverlayFactor)) + if sliderForegroundFrame.maxX - knobWidth <= topX && sliderForegroundFrame.maxX >= topX { + topLineOverlayFactor = 0.0 + } + + let topLineHeight: CGFloat = availableSize.height + let topLineAlpha: CGFloat = topLineOverlayFactor * topLineOverlayFactor + + let topLineFrameTransition = transition + let topLineAlphaTransition = transition + /*if transition.userData(GiftAuctionScreenComponent.IsAdjustingAmountHint.self) != nil { + topLineFrameTransition = .easeInOut(duration: 0.12) + topLineAlphaTransition = .easeInOut(duration: 0.12) + }*/ + + let topLineFrame = CGRect(origin: CGPoint(x: topX, y: (availableSize.height - topLineHeight) * 0.5), size: CGSize(width: 1.0, height: topLineHeight)) + + topLineFrameTransition.setFrame(layer: self.topForegroundLine, frame: topLineFrame) + topLineAlphaTransition.setAlpha(layer: self.topForegroundLine, alpha: topLineAlpha) + topLineFrameTransition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame) + topLineAlphaTransition.setAlpha(layer: self.topBackgroundLine, alpha: topLineAlpha) + + let topTextSize = self.topForegroundText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4))) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let _ = self.topBackgroundText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.strings.SendStarReactions_SliderTop, font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2))) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + + var topTextFrame = CGRect(origin: CGPoint(x: topLineFrame.maxX + 6.0, y: floor((availableSize.height - topTextSize.height) * 0.5)), size: topTextSize) + + let topTextFrameTransition = transition + + let topTextLeftInset: CGFloat = 4.0 + var topTextOverflowWidth: CGFloat = 0.0 + let topTextOverflowState: TopTextOverflowState + if sliderForegroundFrame.maxX < topTextFrame.minX - topTextLeftInset { + topTextOverflowState = .left + } else if sliderForegroundFrame.maxX >= topTextFrame.minX - topTextLeftInset && sliderForegroundFrame.maxX - knobWidth < topTextFrame.maxX + topTextLeftInset { + topTextOverflowWidth = sliderForegroundFrame.maxX - (topTextFrame.minX - topTextLeftInset) + topTextOverflowState = .center + } else { + topTextOverflowState = .right + } + + topTextFrame.origin.x += topTextOverflowWidth + + if let topForegroundTextView = self.topForegroundText.view, let topBackgroundTextView = self.topBackgroundText.view { + if topForegroundTextView.superview == nil { + topBackgroundTextView.layer.anchorPoint = CGPoint() + self.sliderBackground.addSubview(topBackgroundTextView) + + topForegroundTextView.layer.anchorPoint = CGPoint() + self.sliderForeground.addSubview(topForegroundTextView) + } + + var animateTopTextAdditionalX: CGFloat = 0.0 + if transition.userData(GiftAuctionScreenComponent.IsAdjustingAmountHint.self) != nil { + if let previousState = self.topTextOverflowState, previousState != topTextOverflowState, topTextOverflowState.animates(from: previousState) { + animateTopTextAdditionalX = topForegroundTextView.center.x - topTextFrame.origin.x + } + } + + topTextFrameTransition.setPosition(view: topForegroundTextView, position: topTextFrame.origin) + topTextFrameTransition.setPosition(view: topBackgroundTextView, position: topTextFrame.origin) + + topForegroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size) + topBackgroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size) + + if animateTopTextAdditionalX != 0.0 { + topForegroundTextView.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: animateTopTextAdditionalX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.3, damping: 100.0, additive: true) + topBackgroundTextView.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: animateTopTextAdditionalX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.3, damping: 100.0, additive: true) + } + + topForegroundTextView.isHidden = component.topCutoff == nil || topLineFrame.maxX + topTextSize.width + 20.0 > availableSize.width + topBackgroundTextView.isHidden = topForegroundTextView.isHidden + self.topBackgroundLine.isHidden = topX < 10.0 + self.topForegroundLine.isHidden = self.topBackgroundLine.isHidden + } + self.topTextOverflowState = topTextOverflowState + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class GiftAuctionScreenComponent: Component { + final class IsAdjustingAmountHint { + } + + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let gift: StarGift + let auctionContext: GiftAuctionContext + + init( + context: AccountContext, + gift: StarGift, + auctionContext: GiftAuctionContext + ) { + self.context = context + self.gift = gift + self.auctionContext = auctionContext + } + + static func ==(lhs: GiftAuctionScreenComponent, rhs: GiftAuctionScreenComponent) -> Bool { + return true + } + + private struct ItemLayout: Equatable { + var containerSize: CGSize + var containerInset: CGFloat + var containerCornerRadius: CGFloat + var bottomInset: CGFloat + var topInset: CGFloat + + init(containerSize: CGSize, containerInset: CGFloat, containerCornerRadius: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { + self.containerSize = containerSize + self.containerInset = containerInset + self.containerCornerRadius = containerCornerRadius + self.bottomInset = bottomInset + self.topInset = topInset + } + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + } + + private struct Amount: Equatable { + private let sliderSteps: [Int] + private let minRealValue: Int + private let maxRealValue: Int + let maxSliderValue: Int + private let isLogarithmic: Bool + + private(set) var realValue: Int + private(set) var sliderValue: Int + + private static func makeSliderSteps(minRealValue: Int, maxRealValue: Int, isLogarithmic: Bool) -> [Int] { + if isLogarithmic { + var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ] + sliderSteps.removeAll(where: { $0 <= minRealValue }) + sliderSteps.insert(minRealValue, at: 0) + sliderSteps.removeAll(where: { $0 >= maxRealValue }) + sliderSteps.append(maxRealValue) + return sliderSteps + } else { + return [minRealValue, maxRealValue] + } + } + + private static func remapValueToSlider(realValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard realValue >= steps.first!, realValue <= steps.last! else { return 0 } + + for i in 0 ..< steps.count - 1 { + if realValue >= steps[i] && realValue <= steps[i + 1] { + let range = steps[i + 1] - steps[i] + let relativeValue = realValue - steps[i] + let stepFraction = Float(relativeValue) / Float(range) + return Int(Float(i) * Float(maxSliderValue) / Float(steps.count - 1)) + Int(stepFraction * Float(maxSliderValue) / Float(steps.count - 1)) + } + } + return maxSliderValue // Return max slider position if value equals the last step + } + + private static func remapSliderToValue(sliderValue: Int, maxSliderValue: Int, steps: [Int]) -> Int { + guard sliderValue >= 0, sliderValue <= maxSliderValue else { return steps.first! } + + let stepIndex = Int(Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1)) + let fraction = Float(sliderValue) / Float(maxSliderValue) * Float(steps.count - 1) - Float(stepIndex) + + if stepIndex >= steps.count - 1 { + return steps.last! + } else { + let range = steps[stepIndex + 1] - steps[stepIndex] + return steps[stepIndex] + Int(fraction * Float(range)) + } + } + + init(realValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.minRealValue = minRealValue + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.realValue = realValue + self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + init(sliderValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) { + self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic) + self.minRealValue = minRealValue + self.maxRealValue = maxRealValue + self.maxSliderValue = maxSliderValue + self.isLogarithmic = isLogarithmic + + self.sliderValue = sliderValue + self.realValue = Amount.remapSliderToValue(sliderValue: self.sliderValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps) + } + + func withRealValue(_ realValue: Int) -> Amount { + return Amount(realValue: realValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + + func withSliderValue(_ sliderValue: Int) -> Amount { + return Amount(sliderValue: sliderValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } + } + + enum PrivacyPeer: Equatable { + case account + case anonymous + case peer(EnginePeer) + } + + final class View: UIView, UIScrollViewDelegate { + private let dimView: UIView + private let containerView: UIView + private let backgroundLayer: SimpleLayer + private let navigationBarContainer: SparseContainerView + private let scrollView: ScrollView + private let scrollContentClippingView: SparseContainerView + private let scrollContentView: UIView + private let hierarchyTrackingNode: HierarchyTrackingNode + + private var balanceOverlay = ComponentView() + + private let backgroundHandleView: UIImageView + + private let closeButton = ComponentView() + private let infoButton = ComponentView() + + private let title = ComponentView() + + private let badgeStars = BadgeStarsView() + private let sliderBackground = ComponentView() + private let slider = ComponentView() + private let badge = ComponentView() + + private var liveStreamPerks: [ComponentView] = [] + private var liveStreamMessagePreview: ComponentView? + + private var myPeerTitle: ComponentView? + private var myPeerItem: ComponentView? + + private var topPeersTitle: ComponentView? + private var topPeerItems: [EnginePeer.Id: ComponentView] = [:] + + private var giftAuctionState: GiftAuctionContext.State? + private var giftAuctionDisposable: Disposable? + private var giftAuctionTimer: SwiftSignalKit.Timer? + private var peersMap: [EnginePeer.Id: EnginePeer] = [:] + + private let actionButton = ComponentView() + + private let bottomOverscrollLimit: CGFloat + + private var ignoreScrolling: Bool = false + + private var component: GiftAuctionScreenComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + private var environment: ViewControllerComponentContainer.Environment? + private var itemLayout: ItemLayout? + + private var topOffsetDistance: CGFloat? + + private var balance: StarsAmount? + + private var amount: Amount = Amount(realValue: 1, minRealValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true) + private var didChangeAmount: Bool = false + + private var cachedStarImage: (UIImage, PresentationTheme)? + + private var balanceDisposable: Disposable? + + private var badgePhysicsLink: SharedDisplayLinkDriver.Link? + + override init(frame: CGRect) { + self.bottomOverscrollLimit = 200.0 + + self.dimView = UIView() + self.containerView = UIView() + + self.containerView.clipsToBounds = true + self.containerView.layer.cornerRadius = 40.0 + self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.backgroundLayer.cornerRadius = 40.0 + + self.backgroundHandleView = UIImageView() + + self.navigationBarContainer = SparseContainerView() + + self.scrollView = ScrollView() + + self.scrollContentClippingView = SparseContainerView() + self.scrollContentClippingView.clipsToBounds = true + + self.scrollContentView = UIView() + + self.hierarchyTrackingNode = HierarchyTrackingNode() + + super.init(frame: frame) + + self.addSubview(self.dimView) + self.addSubview(self.containerView) + self.containerView.layer.addSublayer(self.backgroundLayer) + + self.scrollView.delaysContentTouches = true + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + + self.containerView.addSubview(self.scrollContentClippingView) + self.scrollContentClippingView.addSubview(self.scrollView) + + self.scrollView.addSubview(self.scrollContentView) + + self.containerView.addSubview(self.navigationBarContainer) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + + self.containerView.addSubnode(self.hierarchyTrackingNode) + + self.hierarchyTrackingNode.updated = { [weak self] value in + guard let self else { + return + } + if value { + if self.badgePhysicsLink == nil { + let badgePhysicsLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in + guard let self else { + return + } + self.updateBadgePhysics() + }) + self.badgePhysicsLink = badgePhysicsLink + } + } else { + if let badgePhysicsLink = self.badgePhysicsLink { + self.badgePhysicsLink = nil + badgePhysicsLink.invalidate() + } + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.balanceDisposable?.dispose() + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + /*guard let itemLayout = self.itemLayout, let topOffsetDistance = self.topOffsetDistance else { + return + } + + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset + topOffset = max(0.0, topOffset) + + if topOffset < topOffsetDistance { + targetContentOffset.pointee.y = scrollView.contentOffset.y + scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true) + }*/ + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + + if let balanceView = self.balanceOverlay.view, let result = balanceView.hitTest(self.convert(point, to: balanceView), with: event) { + return result + } + + if !self.backgroundLayer.frame.contains(point) { + return self.dimView + } + + if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { + return result + } + + if let badgeView = self.badge.view, badgeView.hitTest(self.convert(point, to: badgeView), with: event) != nil { + if let sliderView = self.slider.view as? SliderComponent.View, let hitTestTarget = sliderView.hitTestTarget { + return hitTestTarget + } + } + + let result = super.hitTest(point, with: event) + return result + } + + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + controller.dismiss() + } + } + + private func updateScrolling(transition: ComponentTransition) { + guard let itemLayout = self.itemLayout else { + return + } + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset + topOffset = max(0.0, topOffset) + transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) + + transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) + + var topOffsetFraction = self.scrollView.bounds.minY / 100.0 + topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) + + let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width + let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0 + let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius + + let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction + let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction) + let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction + + var containerTransform = CATransform3DIdentity + containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0) + containerTransform = CATransform3DScale(containerTransform, scale, scale, scale) + transition.setTransform(view: self.containerView, transform: containerTransform) + transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius) + } + + func animateIn() { + self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + if let actionButtonView = self.actionButton.view { + actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + } + + func animateOut(completion: @escaping () -> Void) { + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + + self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + if let actionButtonView = self.actionButton.view { + actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + } + + if let view = self.balanceOverlay.view { + view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false) + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + } + } + + private var previousSliderValue: Float = 0.0 + private var previousTimestamp: Double? + + private var badgeAngularSpeed: CGFloat = 0.0 + private var badgeAngle: CGFloat = 0.0 + private var previousBadgeX: CGFloat? + private var previousPhysicsTimestamp: Double? + + private func updateBadgePhysics() { + let timestamp = CACurrentMediaTime() + + let deltaTime: CGFloat + if let previousPhysicsTimestamp = self.previousPhysicsTimestamp { + deltaTime = CGFloat(min(1.0 / 60.0, timestamp - previousPhysicsTimestamp)) + } else { + deltaTime = CGFloat(1.0 / 60.0) + } + self.previousPhysicsTimestamp = timestamp + + guard let badgeView = self.badge.view as? BadgeComponent.View else { + return + } + let badgeX = badgeView.center.x + + let horizontalVelocity: CGFloat + if let previousBadgeX = self.previousBadgeX { + horizontalVelocity = (badgeX - previousBadgeX) / deltaTime + } else { + horizontalVelocity = 0.0 + } + self.previousBadgeX = badgeX + + let testSpringFriction: CGFloat = 9.0 + let testSpringConstant: CGFloat = 243.0 + + let frictionConstant: CGFloat = testSpringFriction + let springConstant: CGFloat = testSpringConstant + let time: CGFloat = deltaTime + + var badgeAngle = self.badgeAngle + + badgeAngle -= horizontalVelocity * 0.0001 + if abs(badgeAngle) > 0.22 { + badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22 + } + + // friction force = velocity * friction constant + let frictionForce = self.badgeAngularSpeed * frictionConstant + // spring force = (target point - current position) * spring constant + let springForce = -badgeAngle * springConstant + // force = spring force - friction force + let force = springForce - frictionForce + + // velocity = current velocity + force * time / mass + self.badgeAngularSpeed = self.badgeAngularSpeed + force * time + // position = current position + velocity * time + badgeAngle = badgeAngle + self.badgeAngularSpeed * time + badgeAngle = badgeAngle.isNaN ? 0.0 : badgeAngle + + let epsilon: CGFloat = 0.01 + if abs(badgeAngle) < epsilon && abs(self.badgeAngularSpeed) < epsilon { + badgeAngle = 0.0 + self.badgeAngularSpeed = 0.0 + } + + if abs(badgeAngle) > 0.22 { + badgeAngle = badgeAngle < 0.0 ? -0.22 : 0.22 + } + + if self.badgeAngle != badgeAngle { + self.badgeAngle = badgeAngle + badgeView.updateBadgeAngle(angle: self.badgeAngle) + } + } + + private var isLoading = false + private func placeBid() { + guard let component = self.component, case let .generic(gift) = component.gift, let controller = self.environment?.controller() else { + return + } + + let value = Int64(self.amount.realValue) + if let myBidAmount = self.giftAuctionState?.myState.bidAmount { + if value == myBidAmount { + controller.dismiss() + return + } + } + + if let myBidAmount = self.giftAuctionState?.myState.bidAmount, let myMinBidAmount = self.giftAuctionState?.myState.minBidAmount, value < myMinBidAmount { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present( + UndoOverlayController( + presentationData: presentationData, + content: .info(title: nil, text: "Add at least \(myMinBidAmount - myBidAmount) Stars to increase your bid.", timeout: nil, customUndoText: nil), + position: .bottom, + action: { _ in return true } + ), + in: .current + ) + return + } + + self.isLoading = true + self.state?.updated() + + var updateBid = false + if let state = self.giftAuctionState, state.myState.bidAmount != nil { + updateBid = true + } + + let source: BotPaymentInvoiceSource = .starGiftAuctionBid( + hideName: false, + updateBid: updateBid, + peerId: component.context.account.peerId, + giftId: gift.id, + bidAmount: value, + text: nil, + entities: nil + ) + + let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source) + |> `catch` { error -> Signal in + switch error { + case .disallowedStarGifts: + return .fail(.disallowedStarGift) + case .starGiftsUserLimit: + return .fail(.starGiftUserLimit) + default: + return .fail(.generic) + } + } + |> mapToSignal { inputData -> Signal in + return component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) + } + |> deliverOnMainQueue + + let _ = signal.start(next: { [weak self, weak controller] result in + guard let self, let component = self.component else { + return + } + self.isLoading = false + self.state?.updated() + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller?.present( + UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: "Your bid has been placed", text: "If you fall below the top 50, your bid will roll over to the next drop.", cancel: nil, destructive: false), + position: .bottom, + action: { _ in return true } + ), + in: .current + ) + + component.context.starsContext?.load(force: true) + }) + } + + func update(component: GiftAuctionScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let themeUpdated = self.environment?.theme !== environment.theme + + let resetScrolling = self.scrollView.bounds.width != availableSize.width + + let fillingSize: CGFloat + if case .regular = environment.metrics.widthClass { + fillingSize = min(availableSize.width, 414.0) - environment.safeInsets.left * 2.0 + } else { + fillingSize = min(availableSize.width, 428.0) - environment.safeInsets.left * 2.0 + } + let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 24.0 + + let context = component.context + let balanceSize = self.balanceOverlay.update( + transition: .immediate, + component: AnyComponent( + StarsBalanceOverlayComponent( + context: component.context, + peerId: component.context.account.peerId, + theme: environment.theme, + currency: .stars, + action: { [weak self] in + guard let self, let starsContext = context.starsContext, let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { + return + } + self.environment?.controller()?.dismiss() + + let _ = (context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { options in + let controller = context.sharedContext.makeStarsPurchaseScreen( + context: context, + starsContext: starsContext, + options: options, + purpose: .generic, + targetPeerId: nil, + customTheme: environment.theme, + completion: { _ in } + ) + navigationController.pushViewController(controller) + }) + } + ) + ), + environment: {}, + containerSize: availableSize + ) + if let view = self.balanceOverlay.view { + if view.superview == nil { + self.addSubview(view) + + view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: environment.statusBarHeight + 5.0), size: balanceSize) + } + + if self.component == nil { + if let starsContext = component.context.starsContext { + self.balanceDisposable = (starsContext.state + |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { + return + } + if let state { + if self.balance != state.balance { + self.balance = state.balance + self.state?.updated(transition: .immediate) + } + } + }) + } + + let context = component.context + self.giftAuctionDisposable = (component.auctionContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + let isFirstTime = self.giftAuctionState == nil + self.giftAuctionState = state + + var peerIds: [EnginePeer.Id] = [] + if case let .ongoing(_, _, _, topBidders, _, _, _, _) = state?.auctionState { + for bidder in topBidders { + if self.peersMap[bidder] == nil { + peerIds.append(bidder) + } + } + } + + var transition = ComponentTransition.spring(duration: 0.4) + + if isFirstTime { + peerIds.append(context.account.peerId) + + var minBidAmount: Int64 = 100 + if case let .ongoing(_, auctionMinBidAmount, _, _, _, _, _, _) = state?.auctionState { + minBidAmount = auctionMinBidAmount + } + var currentValue = max(Int(minBidAmount), 100) + if let myBidAmount = state?.myState.bidAmount { + currentValue = Int(myBidAmount) + } + self.amount = Amount(realValue: currentValue, minRealValue: Int(minBidAmount), maxRealValue: 30000, maxSliderValue: 999, isLogarithmic: true) + transition = .immediate + } + + if !peerIds.isEmpty { + let _ = (context.engine.data.get(EngineDataMap( + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in + return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + } + )) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peers in + guard let self else { + return + } + var peersMap: [EnginePeer.Id: EnginePeer] = self.peersMap + for (peerId, maybePeer) in peers { + if let peer = maybePeer { + peersMap[peerId] = peer + } + } + self.peersMap = peersMap + self.state?.updated(transition: transition) + }) + } + self.state?.updated(transition: transition) + }) + + self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.state?.updated() + }, queue: Queue.mainQueue()) + self.giftAuctionTimer?.start() + } + + self.component = component + self.state = state + self.environment = environment + + if themeUpdated { + self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor + + var locations: [NSNumber] = [] + var colors: [CGColor] = [] + let numStops = 6 + for i in 0 ..< numStops { + let step = CGFloat(i) / CGFloat(numStops - 1) + locations.append(step as NSNumber) + colors.append(environment.theme.list.blocksBackgroundColor.withAlphaComponent(1.0 - step * step).cgColor) + } + } + + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + var contentHeight: CGFloat = 0.0 + + let sliderInset: CGFloat = sideInset + 8.0 + let sliderSize = self.slider.update( + transition: transition, + component: AnyComponent(SliderComponent( + content: .discrete(SliderComponent.Discrete( + valueCount: self.amount.maxSliderValue + 1, + value: self.amount.sliderValue, + markPositions: false, + valueUpdated: { [weak self] value in + guard let self else { + return + } + + let maxAmount: Int = 1000 + + self.amount = self.amount.withSliderValue(value) + self.didChangeAmount = true + + self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint())) + + let sliderValue = Float(value) / Float(maxAmount) + let currentTimestamp = CACurrentMediaTime() + + if let previousTimestamp { + let deltaTime = currentTimestamp - previousTimestamp + let delta = sliderValue - self.previousSliderValue + let deltaValue = abs(sliderValue - self.previousSliderValue) + + let speed = deltaValue / Float(deltaTime) + let newSpeed = max(0, min(65.0, speed * 70.0)) + + if newSpeed < 0.01 && deltaValue < 0.001 { + } else { + self.badgeStars.update(speed: newSpeed, delta: delta) + } + } + + self.previousSliderValue = sliderValue + self.previousTimestamp = currentTimestamp + } + )), + trackBackgroundColor: .clear, + trackForegroundColor: .clear, + knobSize: 26.0, + knobColor: .white, + isTrackingUpdated: { [weak self] isTracking in + guard let self else { + return + } + if !isTracking { + self.previousTimestamp = nil + self.badgeStars.update(speed: 0.0) + } + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sliderInset * 2.0, height: 30.0) + ) + + contentHeight += 148.0 + + let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight), size: sliderSize) + let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0)) + + let progressFraction: CGFloat = CGFloat(self.amount.sliderValue) / CGFloat(self.amount.maxSliderValue) + + var sliderColor: UIColor = UIColor(rgb: 0xFFB10D) + + let color = GroupCallMessagesContext.getStarAmountParamMapping(value: Int64(self.amount.realValue)).color ?? .purple + sliderColor = StoryLiveChatMessageComponent.getMessageColor(color: color) + + let _ = self.sliderBackground.update( + transition: transition, + component: AnyComponent(SliderBackgroundComponent( + theme: environment.theme, + strings: environment.strings, + value: progressFraction, + topCutoff: nil, + color: sliderColor + )), + environment: {}, + containerSize: sliderBackgroundFrame.size + ) + + if let sliderView = self.slider.view, let sliderBackgroundView = self.sliderBackground.view { + if sliderView.superview == nil { + self.scrollContentView.addSubview(self.badgeStars) + self.scrollContentView.addSubview(sliderBackgroundView) + self.scrollContentView.addSubview(sliderView) + } + transition.setFrame(view: sliderView, frame: sliderFrame) + + transition.setFrame(view: sliderBackgroundView, frame: sliderBackgroundFrame) + + var subtitle: String? + let badgeValue = self.amount.realValue + + if let myBidAmount = self.giftAuctionState?.myState.bidAmount { + if self.amount.realValue > myBidAmount { + subtitle = "+\(badgeValue - Int(myBidAmount))" + } else if myBidAmount == self.amount.realValue { + subtitle = "your bid" + } + } + + let badgeSize = self.badge.update( + transition: transition, + component: AnyComponent(BadgeComponent( + theme: environment.theme, + prefix: nil, + title: "\(badgeValue)", + subtitle: subtitle, + color: sliderColor + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 200.0) + ) + + let sliderMinWidth = sliderBackgroundFrame.height + let sliderAreaWidth: CGFloat = sliderBackgroundFrame.width - sliderMinWidth + let sliderForegroundFrame = CGRect(origin: sliderBackgroundFrame.origin, size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * progressFraction), height: sliderBackgroundFrame.height)) + + var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize) + if let badgeView = self.badge.view as? BadgeComponent.View { + if badgeView.superview == nil { + self.scrollContentView.insertSubview(badgeView, belowSubview: self.badgeStars) + } + + let badgeSideInset = sideInset + 15.0 + + let badgeOverflowWidth: CGFloat + if badgeFrame.minX - badgeSize.width * 0.5 < badgeSideInset { + badgeOverflowWidth = badgeSideInset - (badgeFrame.minX - badgeSize.width * 0.5) + } else if badgeFrame.minX + badgeSize.width * 0.5 > availableSize.width - badgeSideInset { + badgeOverflowWidth = availableSize.width - badgeSideInset - (badgeFrame.minX + badgeSize.width * 0.5) + } else { + badgeOverflowWidth = 0.0 + } + + badgeFrame.origin.x += badgeOverflowWidth + + badgeView.frame = badgeFrame + + badgeView.adjustTail(size: badgeSize, overflowWidth: -badgeOverflowWidth) + } + + let starsRect = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: sliderForegroundFrame.midY)) + self.badgeStars.frame = starsRect + self.badgeStars.update(size: starsRect.size, color: sliderColor, emitterPosition: CGPoint(x: badgeFrame.minX, y: badgeFrame.midY - 64.0)) + } + + var perks: [([AnimatedTextComponent.Item], String)] = [] + + var minBidAnimatedItems: [AnimatedTextComponent.Item] = [] + var untilNextDropAnimatedItems: [AnimatedTextComponent.Item] = [] + var dropsLeftAnimatedItems: [AnimatedTextComponent.Item] = [] + + if let auctionState = self.giftAuctionState?.auctionState { + if case let .ongoing(_, minBidAmount, _, _, _, nextDropDate, dropsLeft, _) = auctionState { + var minBidAmount = minBidAmount + if let myMinBidAmmount = self.giftAuctionState?.myState.minBidAmount { + minBidAmount = myMinBidAmmount + } + let minBidString = "# \(presentationStringsFormattedNumber(Int32(clamping: minBidAmount), environment.dateTimeFormat.groupingSeparator))" + if let hashIndex = minBidString.firstIndex(of: "#") { + var prefix = String(minBidString[.. + if self.liveStreamPerks.count > i { + perkView = self.liveStreamPerks[i] + } else { + perkView = ComponentView() + self.liveStreamPerks.append(perkView) + } + let perk = perks[i] + let _ = perkView.update( + transition: transition, + component: AnyComponent(AuctionStatComponent( + title: perk.0, + subtitle: perk.1, + theme: environment.theme + )), + environment: {}, + containerSize: perkFrame.size + ) + if let perkComponentView = perkView.view { + if perkComponentView.superview == nil { + self.scrollContentView.addSubview(perkComponentView) + } + transition.setFrame(view: perkComponentView, frame: perkFrame) + } + } + + contentHeight += perkHeight + contentHeight += 24.0 + + if self.backgroundHandleView.image == nil { + self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate) + } + self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07) + let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0)) + if self.backgroundHandleView.superview == nil { + self.navigationBarContainer.addSubview(self.backgroundHandleView) + } + transition.setFrame(view: self.backgroundHandleView, frame: backgroundHandleFrame) + + let closeButtonSize = self.closeButton.update( + transition: .immediate, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let closeButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.navigationBarContainer.addSubview(closeButtonView) + } + transition.setFrame(view: closeButtonView, frame: closeButtonFrame) + } + + let infoButtonSize = self.infoButton.update( + transition: .immediate, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "info", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Info", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + let giftController = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, gift: component.gift, completion: {}) + self.environment?.controller()?.push(giftController) + } + )), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let infoButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - infoButtonSize.width, y: 16.0), size: infoButtonSize) + if let infoButtonView = self.infoButton.view { + if infoButtonView.superview == nil { + self.navigationBarContainer.addSubview(infoButtonView) + } + transition.setFrame(view: infoButtonView, frame: infoButtonFrame) + } + + let containerInset: CGFloat = environment.statusBarHeight + 10.0 + + var initialContentHeight = contentHeight + let clippingY: CGFloat + + let title = self.title + let actionButton = self.actionButton + + let titleText = "Place a Bid" + let titleSize = title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: 26.0), size: titleSize) + if let titleView = title.view { + if titleView.superview == nil { + self.navigationBarContainer.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + var myBidTitleComponent: AnyComponent? + var myBidComponent: AnyComponent? + + var topBidsTitleComponent: AnyComponent? + var topBidsComponents: [(EnginePeer.Id, AnyComponent)] = [] + + if let giftAuctionState = self.giftAuctionState, case let .ongoing(_, _, bidLevels, topBidders, _, _, _, _) = giftAuctionState.auctionState { + if let myBid = giftAuctionState.myState.bidAmount, let myBidDate = giftAuctionState.myState.bidDate, let peer = self.peersMap[component.context.account.peerId] { + var place: Int32 = 1 + for level in bidLevels { + if myBid < level.amount || (myBid == level.amount && myBidDate > level.date) { + place = level.position + 1 + } + } + myBidTitleComponent = AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "YOUR BID", font: Font.medium(13.0), textColor: environment.theme.list.itemSecondaryTextColor)))) + myBidComponent = AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: place, amount: myBid, isLast: true)) + } + + var i: Int32 = 1 + for bidder in topBidders { + if let peer = self.peersMap[bidder] { + var bid: Int64 = 0 + for level in bidLevels { + if level.position == i { + bid = level.amount + break + } + } + topBidsComponents.append((bidder, AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: i, amount: bid, isLast: i == topBidders.count)))) + } + i += 1 + } + + if !topBidsComponents.isEmpty { + topBidsTitleComponent = AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "TOP 3 WINNERS", font: Font.medium(13.0), textColor: environment.theme.list.itemSecondaryTextColor)))) + } + } + + if let myBidTitleComponent, let myBidComponent { + let myPeerTitle: ComponentView + let myPeerItem: ComponentView + + if let currentTitle = self.myPeerTitle, let currentItem = self.myPeerItem { + myPeerTitle = currentTitle + myPeerItem = currentItem + } else { + myPeerTitle = ComponentView() + self.myPeerTitle = myPeerTitle + myPeerItem = ComponentView() + self.myPeerItem = myPeerItem + } + + let myPeerTitleSize = myPeerTitle.update( + transition: .immediate, + component: myBidTitleComponent, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + let myPeerTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: myPeerTitleSize) + if let myPeerTitleView = myPeerTitle.view { + if myPeerTitleView.superview == nil { + self.scrollContentView.addSubview(myPeerTitleView) + } + myPeerTitleView.frame = myPeerTitleFrame + } + contentHeight += myPeerTitleSize.height + contentHeight += 7.0 + + let myPeerItemSize = myPeerItem.update( + transition: .immediate, + component: myBidComponent, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + let myPeerItemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: myPeerItemSize) + if let myPeerItemView = myPeerItem.view { + if myPeerItemView.superview == nil { + self.scrollContentView.addSubview(myPeerItemView) + } + myPeerItemView.frame = myPeerItemFrame + } + contentHeight += myPeerItemSize.height + contentHeight += 8.0 + } else if let myPeerTitle = self.myPeerTitle, let myPeerItem = self.myPeerItem { + self.myPeerTitle = nil + self.myPeerItem = nil + + if let myPeerTitleView = myPeerTitle.view, let myPeerItemView = myPeerItem.view { + transition.setAlpha(view: myPeerTitleView, alpha: 0.0, completion: { _ in + myPeerTitleView.removeFromSuperview() + }) + transition.setAlpha(view: myPeerItemView, alpha: 0.0, completion: { _ in + myPeerItemView.removeFromSuperview() + }) + } + } + + if let topBidsTitleComponent { + let topPeersTitle: ComponentView + if let currentTitle = self.topPeersTitle { + topPeersTitle = currentTitle + } else { + topPeersTitle = ComponentView() + self.topPeersTitle = topPeersTitle + } + + let topPeersTitleSize = topPeersTitle.update( + transition: .immediate, + component: topBidsTitleComponent, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + let topPeersTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: topPeersTitleSize) + if let topPeersTitleView = topPeersTitle.view { + if topPeersTitleView.superview == nil { + self.scrollContentView.addSubview(topPeersTitleView) + } + topPeersTitleView.frame = topPeersTitleFrame + } + contentHeight += topPeersTitleSize.height + contentHeight += 7.0 + + var validKeys: Set = Set() + for (peerId, topBidItemComponent) in topBidsComponents { + validKeys.insert(peerId) + + let topPeerItem: ComponentView + if let current = self.topPeerItems[peerId] { + topPeerItem = current + } else { + topPeerItem = ComponentView() + self.topPeerItems[peerId] = topPeerItem + } + + let topPeerItemSize = topPeerItem.update( + transition: .immediate, + component: topBidItemComponent, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + let topPeerItemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: topPeerItemSize) + if let topPeerItemView = topPeerItem.view { + if topPeerItemView.superview == nil { + self.scrollContentView.addSubview(topPeerItemView) + } + topPeerItemView.frame = topPeerItemFrame + } + contentHeight += topPeerItemSize.height + } + + var removeKeys: [EnginePeer.Id] = [] + for (peerId, item) in self.topPeerItems { + if !validKeys.contains(peerId) { + removeKeys.append(peerId) + + if let itemView = item.view { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for id in removeKeys { + self.topPeerItems.removeValue(forKey: id) + } + + contentHeight += 16.0 + } else if let topPeersTitle = self.topPeersTitle { + self.topPeersTitle = nil + if let topPeersTitleView = topPeersTitle.view { + transition.setAlpha(view: topPeersTitleView, alpha: 0.0, completion: { _ in + topPeersTitleView.removeFromSuperview() + }) + } + for (_, item) in self.topPeerItems { + if let itemView = item.view { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + self.topPeerItems = [:] + } + +// if !reactData.topPeers.isEmpty { +// contentHeight += 3.0 +// +// if case .message = reactData.reactSubject { +// let topPeersLeftSeparator: SimpleLayer +// if let current = self.topPeersLeftSeparator { +// topPeersLeftSeparator = current +// } else { +// topPeersLeftSeparator = SimpleLayer() +// self.topPeersLeftSeparator = topPeersLeftSeparator +// self.scrollContentView.layer.addSublayer(topPeersLeftSeparator) +// } +// +// let topPeersRightSeparator: SimpleLayer +// if let current = self.topPeersRightSeparator { +// topPeersRightSeparator = current +// } else { +// topPeersRightSeparator = SimpleLayer() +// self.topPeersRightSeparator = topPeersRightSeparator +// self.scrollContentView.layer.addSublayer(topPeersRightSeparator) +// } +// +// let topPeersTitleBackground: SimpleLayer +// if let current = self.topPeersTitleBackground { +// topPeersTitleBackground = current +// } else { +// topPeersTitleBackground = SimpleLayer() +// self.topPeersTitleBackground = topPeersTitleBackground +// self.scrollContentView.layer.addSublayer(topPeersTitleBackground) +// } +// +// let topPeersTitle: ComponentView +// if let current = self.topPeersTitle { +// topPeersTitle = current +// } else { +// topPeersTitle = ComponentView() +// self.topPeersTitle = topPeersTitle +// } +// +// topPeersLeftSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor +// topPeersRightSeparator.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor +// +// let topPeersTitleSize = topPeersTitle.update( +// transition: .immediate, +// component: AnyComponent(MultilineTextComponent( +// text: .plain(NSAttributedString(string: environment.strings.SendStarReactions_SectionTop, font: Font.semibold(15.0), textColor: .white)) +// )), +// environment: {}, +// containerSize: CGSize(width: 300.0, height: 100.0) +// ) +// let topPeersBackgroundSize = CGSize(width: topPeersTitleSize.width + 16.0 * 2.0, height: topPeersTitleSize.height + 9.0 * 2.0) +// let topPeersBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - topPeersBackgroundSize.width) * 0.5), y: contentHeight), size: topPeersBackgroundSize) +// +// topPeersTitleBackground.backgroundColor = UIColor(rgb: 0xFFB10D).cgColor +// topPeersTitleBackground.cornerRadius = topPeersBackgroundFrame.height * 0.5 +// transition.setFrame(layer: topPeersTitleBackground, frame: topPeersBackgroundFrame) +// +// let topPeersTitleFrame = CGRect(origin: CGPoint(x: topPeersBackgroundFrame.minX + floor((topPeersBackgroundFrame.width - topPeersTitleSize.width) * 0.5), y: topPeersBackgroundFrame.minY + floor((topPeersBackgroundFrame.height - topPeersTitleSize.height) * 0.5)), size: topPeersTitleSize) +// if let topPeersTitleView = topPeersTitle.view { +// if topPeersTitleView.superview == nil { +// self.scrollContentView.addSubview(topPeersTitleView) +// } +// transition.setFrame(view: topPeersTitleView, frame: topPeersTitleFrame) +// } +// +// let separatorY = topPeersBackgroundFrame.midY +// let separatorSpacing: CGFloat = 10.0 +// transition.setFrame(layer: topPeersLeftSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: separatorY), size: CGSize(width: max(0.0, topPeersBackgroundFrame.minX - separatorSpacing - sideInset), height: UIScreenPixel))) +// transition.setFrame(layer: topPeersRightSeparator, frame: CGRect(origin: CGPoint(x: topPeersBackgroundFrame.maxX + separatorSpacing, y: separatorY), size: CGSize(width: max(0.0, availableSize.width - sideInset - (topPeersBackgroundFrame.maxX + separatorSpacing)), height: UIScreenPixel))) +// +// contentHeight += 60.0 +// } +// +// var mappedTopPeers = reactData.topPeers +// if let index = mappedTopPeers.firstIndex(where: { $0.isMy }) { +// mappedTopPeers.remove(at: index) +// } +// +// var myCount = 0 +// if let myTopPeer = reactData.myTopPeer { +// myCount += myTopPeer.count +// } +// var myCountAddition = 0 +// if self.didChangeAmount { +// myCountAddition = Int(self.amount.realValue) +// } +// myCount += myCountAddition +// if myCount != 0 { +// var topPeer: EnginePeer? +// switch self.privacyPeer { +// case .anonymous: +// topPeer = nil +// case .account: +// topPeer = reactData.myPeer +// case let .peer(peer): +// topPeer = peer +// } +// +// mappedTopPeers.append(GiftAuctionScreen.TopPeer( +// randomIndex: -1, +// peer: topPeer, +// isMy: true, +// count: myCount +// )) +// } +// mappedTopPeers.sort(by: { $0.count > $1.count }) +// if mappedTopPeers.count > 3 { +// mappedTopPeers = Array(mappedTopPeers.prefix(3)) +// } +// +// var animateItems = false +// var itemPositionTransition = transition +// var itemAlphaTransition = transition +// if transition.userData(IsAdjustingAmountHint.self) != nil { +// animateItems = true +// itemPositionTransition = .spring(duration: 0.3) +// itemAlphaTransition = .easeInOut(duration: 0.15) +// } +// +// var validIds: [GiftAuctionScreen.TopPeer.Id] = [] +// var items: [(itemView: ComponentView, size: CGSize)] = [] +// for topPeer in mappedTopPeers { +// validIds.append(topPeer.id) +// +// let itemView: ComponentView +// if let current = self.topPeerItems[topPeer.id] { +// itemView = current +// } else { +// itemView = ComponentView() +// self.topPeerItems[topPeer.id] = itemView +// } +// +// let itemCountString = presentationStringsFormattedNumber(Int32(topPeer.count), environment.dateTimeFormat.groupingSeparator) +// +// var peerColor: UIColor = UIColor(rgb: 0xFFB10D) +// if case .liveStream = reactData.reactSubject { +// let color = GroupCallMessagesContext.getStarAmountParamMapping(value: Int64(topPeer.count)).color ?? .purple +// peerColor = StoryLiveChatMessageComponent.getMessageColor(color: color) +// } +// +// let itemSize = itemView.update( +// transition: .immediate, +// component: AnyComponent(PlainButtonComponent( +// content: AnyComponent(PeerComponent( +// context: component.context, +// theme: environment.theme, +// strings: environment.strings, +// peer: topPeer.peer, +// count: itemCountString, +// color: peerColor +// )), +// effectAlignment: .center, +// action: { [weak self] in +// guard let self, let component = self.component, let peer = topPeer.peer else { +// return +// } +// guard let controller = self.environment?.controller() else { +// return +// } +// guard let navigationController = controller.navigationController as? NavigationController else { +// return +// } +// var viewControllers = navigationController.viewControllers +// guard let index = viewControllers.firstIndex(where: { $0 === controller }) else { +// return +// } +// +// let context = component.context +// +// if case .user = peer { +// if let peerInfoController = context.sharedContext.makePeerInfoController( +// context: context, +// updatedPresentationData: nil, +// peer: peer._asPeer(), +// mode: .generic, +// avatarInitiallyExpanded: false, +// fromChat: false, +// requestsContext: nil +// ) { +// viewControllers.insert(peerInfoController, at: index) +// } +// } else { +// let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) +// viewControllers.insert(chatController, at: index) +// } +// navigationController.setViewControllers(viewControllers, animated: true) +// controller.dismiss() +// }, +// isEnabled: topPeer.peer != nil && topPeer.peer?.id != component.context.account.peerId, +// animateAlpha: false +// )), +// environment: {}, +// containerSize: CGSize(width: 200.0, height: 200.0) +// ) +// items.append((itemView, itemSize)) +// } +// var removedIds: [GiftAuctionScreen.TopPeer.Id] = [] +// for (id, itemView) in self.topPeerItems { +// if !validIds.contains(id) { +// removedIds.append(id) +// +// if animateItems { +// if let itemComponentView = itemView.view { +// itemPositionTransition.setScale(view: itemComponentView, scale: 0.001) +// itemAlphaTransition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in +// itemComponentView?.removeFromSuperview() +// }) +// } +// } else { +// itemView.view?.removeFromSuperview() +// } +// } +// } +// for id in removedIds { +// self.topPeerItems.removeValue(forKey: id) +// } +// +// var itemsWidth: CGFloat = 0.0 +// for (_, itemSize) in items { +// itemsWidth += itemSize.width +// } +// +// let maxItemSpacing = 48.0 +// var itemSpacing = floor((availableSize.width - itemsWidth) / CGFloat(items.count + 1)) +// itemSpacing = min(itemSpacing, maxItemSpacing) +// +// let totalWidth = itemsWidth + itemSpacing * CGFloat(items.count + 1) +// var itemX: CGFloat = floor((availableSize.width - totalWidth) * 0.5) + itemSpacing +// for (itemView, itemSize) in items { +// if let itemComponentView = itemView.view { +// var animateItem = animateItems +// if itemComponentView.superview == nil { +// self.scrollContentView.addSubview(itemComponentView) +// animateItem = false +// ComponentTransition.immediate.setScale(view: itemComponentView, scale: 0.001) +// itemComponentView.alpha = 0.0 +// } +// +// let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight), size: itemSize) +// +// if animateItem { +// itemPositionTransition.setPosition(view: itemComponentView, position: itemFrame.center) +// itemPositionTransition.setBounds(view: itemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) +// } else { +// itemComponentView.center = itemFrame.center +// itemComponentView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) +// } +// +// itemPositionTransition.setScale(view: itemComponentView, scale: 1.0) +// itemAlphaTransition.setAlpha(view: itemComponentView, alpha: 1.0) +// } +// itemX += itemSize.width + itemSpacing +// } +// +// contentHeight += 104.0 +// } + + initialContentHeight = contentHeight + + if self.cachedStarImage == nil || self.cachedStarImage?.1 !== environment.theme { + self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme) + } + + var formattedAmount = presentationStringsFormattedNumber(Int32(clamping: self.amount.realValue), environment.dateTimeFormat.groupingSeparator) + let buttonString: String + if let myBidAmount = self.giftAuctionState?.myState.bidAmount { + if myBidAmount == self.amount.realValue { + buttonString = environment.strings.Common_OK + } else { + formattedAmount = presentationStringsFormattedNumber(Int32(clamping: self.amount.realValue - Int(myBidAmount)), environment.dateTimeFormat.groupingSeparator) + buttonString = "Add # \(formattedAmount) to Your Bid" + } + } else { + buttonString = "Place a # \(formattedAmount) Bid" + } + let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 { + buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) + } + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 54.0, sideInset: 32.0) + + let actionButtonSize = actionButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 54.0 * 0.5 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + ), + isEnabled: true, + displaysProgress: self.isLoading, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + guard let balance = self.balance else { + return + } + + if balance < StarsAmount(value: Int64(self.amount.realValue), nanos: 0) { + let _ = (component.context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] options in + guard let self, let component = self.component else { + return + } + guard let starsContext = component.context.starsContext else { + return + } + + let purchasePurpose: StarsPurchasePurpose = .generic + let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: purchasePurpose, targetPeerId: nil, customTheme: environment.theme, completion: { result in + let _ = result + //TODO:release + }) + self.environment?.controller()?.push(purchaseScreen) + self.environment?.controller()?.dismiss() + }) + + return + } + + self.placeBid() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 54.0) + ) + + + + var bottomPanelHeight = 13.0 + buttonInsets.bottom + actionButtonSize.height + let actionButtonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: availableSize.height - buttonInsets.bottom - actionButtonSize.height), size: actionButtonSize) + bottomPanelHeight -= 1.0 + if let actionButtonView = actionButton.view { + if actionButtonView.superview == nil { + self.containerView.addSubview(actionButtonView) + } + transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + } + + contentHeight += bottomPanelHeight + initialContentHeight += bottomPanelHeight + + clippingY = actionButtonFrame.minY - 24.0 + + let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) + + let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) + + self.scrollContentClippingView.layer.cornerRadius = 10.0 + + self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: environment.deviceMetrics.screenCornerRadius, bottomInset: environment.safeInsets.bottom, topInset: topInset) + + transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) + + transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) + transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height))) + + let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset)) + transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) + transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) + + self.ignoreScrolling = true + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) + let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) + if contentSize != self.scrollView.contentSize { + self.scrollView.contentSize = contentSize + } + if resetScrolling { + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) + + transition.setPosition(view: self.containerView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.containerView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + + if let controller = environment.controller(), !controller.automaticallyControlPresentationContextLayout { + let bottomInset: CGFloat = contentHeight - 12.0 + + let layout = ContainerViewLayout( + size: availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class GiftAuctionScreen: ViewControllerComponentContainer { + public final class TransitionOut { + public let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + } + + private let context: AccountContext + + private var didPlayAppearAnimation: Bool = false + private var isDismissed: Bool = false + + public init(context: AccountContext, gift: StarGift, auctionContext: GiftAuctionContext) { + self.context = context + + super.init(context: context, component: GiftAuctionScreenComponent( + context: context, + gift: gift, + auctionContext: auctionContext + ), navigationBarAppearance: .none, theme: .default) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + + if !self.didPlayAppearAnimation { + self.didPlayAppearAnimation = true + + if let componentView = self.node.hostView.componentView as? GiftAuctionScreenComponent.View { + componentView.animateIn() + } + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + + if let componentView = self.node.hostView.componentView as? GiftAuctionScreenComponent.View { + componentView.animateOut(completion: { [weak self] in + completion?() + self?.dismiss(animated: false) + }) + } else { + self.dismiss(animated: false) + } + } + } +} + +private final class BadgeStarsView: UIView { + private let staticEmitterLayer = CAEmitterLayer() + private let dynamicEmitterLayer = CAEmitterLayer() + private var currentColor: UIColor? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.staticEmitterLayer) + self.layer.addSublayer(self.dynamicEmitterLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + private func setupEmitter() { + guard let currentColor = self.currentColor else { + return + } + let color = currentColor + + self.staticEmitterLayer.emitterShape = .circle + self.staticEmitterLayer.emitterSize = CGSize(width: 10.0, height: 5.0) + self.staticEmitterLayer.emitterMode = .outline + self.layer.addSublayer(self.staticEmitterLayer) + + self.dynamicEmitterLayer.birthRate = 0.0 + self.dynamicEmitterLayer.emitterShape = .circle + self.dynamicEmitterLayer.emitterSize = CGSize(width: 10.0, height: 55.0) + self.dynamicEmitterLayer.emitterMode = .surface + self.layer.addSublayer(self.dynamicEmitterLayer) + + let staticEmitter = CAEmitterCell() + staticEmitter.name = "emitter" + staticEmitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + staticEmitter.birthRate = 20.0 + staticEmitter.lifetime = 2.7 + staticEmitter.velocity = 30.0 + staticEmitter.velocityRange = 3 + staticEmitter.scale = 0.15 + staticEmitter.scaleRange = 0.08 + staticEmitter.emissionRange = .pi * 2.0 + staticEmitter.setValue(3.0, forKey: "mass") + staticEmitter.setValue(2.0, forKey: "massRange") + + let dynamicEmitter = CAEmitterCell() + dynamicEmitter.name = "emitter" + dynamicEmitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + dynamicEmitter.birthRate = 0.0 + dynamicEmitter.lifetime = 2.7 + dynamicEmitter.velocity = 30.0 + dynamicEmitter.velocityRange = 3 + dynamicEmitter.scale = 0.15 + dynamicEmitter.scaleRange = 0.08 + dynamicEmitter.emissionRange = .pi / 3.0 + dynamicEmitter.setValue(3.0, forKey: "mass") + dynamicEmitter.setValue(2.0, forKey: "massRange") + + let staticColors: [Any] = [ + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.35).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + staticEmitter.setValue([staticColorBehavior], forKey: "emitterBehaviors") + + let dynamicColors: [Any] = [ + UIColor.white.withAlphaComponent(0.35).cgColor, + color.withAlphaComponent(0.85).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let dynamicColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + dynamicColorBehavior.setValue(dynamicColors, forKey: "colors") + dynamicEmitter.setValue([dynamicColorBehavior], forKey: "emitterBehaviors") + + let attractor = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor") + attractor.setValue("attractor", forKey: "name") + attractor.setValue(20, forKey: "falloff") + attractor.setValue(35, forKey: "radius") + self.staticEmitterLayer.setValue([attractor], forKey: "emitterBehaviors") + self.staticEmitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.attractor.stiffness") + self.staticEmitterLayer.setValue(false, forKeyPath: "emitterBehaviors.attractor.enabled") + + self.staticEmitterLayer.emitterCells = [staticEmitter] + self.dynamicEmitterLayer.emitterCells = [dynamicEmitter] + } + + func update(speed: Float, delta: Float? = nil) { + if speed > 0.0 { + if self.dynamicEmitterLayer.birthRate.isZero { + self.dynamicEmitterLayer.beginTime = CACurrentMediaTime() + } + + self.dynamicEmitterLayer.setValue(Float(20.0 + speed * 1.4), forKeyPath: "emitterCells.emitter.birthRate") + self.dynamicEmitterLayer.setValue(2.7 - min(1.1, 1.5 * speed / 120.0), forKeyPath: "emitterCells.emitter.lifetime") + self.dynamicEmitterLayer.setValue(30.0 + CGFloat(speed / 80.0), forKeyPath: "emitterCells.emitter.velocity") + + if let delta, speed > 15.0 { + self.dynamicEmitterLayer.setValue(delta > 0 ? .pi : 0, forKeyPath: "emitterCells.emitter.emissionLongitude") + self.dynamicEmitterLayer.setValue(.pi / 2.0, forKeyPath: "emitterCells.emitter.emissionRange") + } else { + self.dynamicEmitterLayer.setValue(0.0, forKeyPath: "emitterCells.emitter.emissionLongitude") + self.dynamicEmitterLayer.setValue(.pi * 2.0, forKeyPath: "emitterCells.emitter.emissionRange") + } + self.staticEmitterLayer.setValue(true, forKeyPath: "emitterBehaviors.attractor.enabled") + + self.dynamicEmitterLayer.birthRate = 1.0 + self.staticEmitterLayer.birthRate = 0.0 + } else { + self.dynamicEmitterLayer.birthRate = 0.0 + + if let staticEmitter = self.staticEmitterLayer.emitterCells?.first { + staticEmitter.beginTime = CACurrentMediaTime() + } + self.staticEmitterLayer.birthRate = 1.0 + self.staticEmitterLayer.setValue(false, forKeyPath: "emitterBehaviors.attractor.enabled") + } + } + + func update(size: CGSize, color: UIColor, emitterPosition: CGPoint) { + if self.staticEmitterLayer.emitterCells == nil { + self.currentColor = color + self.setupEmitter() + } else if self.currentColor != color { + self.currentColor = color + + let staticColors: [Any] = [ + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.35).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + + let dynamicColors: [Any] = [ + UIColor.white.withAlphaComponent(0.35).cgColor, + color.withAlphaComponent(0.85).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let dynamicColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + dynamicColorBehavior.setValue(dynamicColors, forKey: "colors") + + for cell in self.staticEmitterLayer.emitterCells ?? [] { + cell.setValue([staticColorBehavior], forKey: "emitterBehaviors") + } + for cell in self.dynamicEmitterLayer.emitterCells ?? [] { + cell.setValue([dynamicColorBehavior], forKey: "emitterBehaviors") + } + } + + self.staticEmitterLayer.frame = CGRect(origin: .zero, size: size) + self.staticEmitterLayer.emitterPosition = emitterPosition + + self.dynamicEmitterLayer.frame = CGRect(origin: .zero, size: size) + self.dynamicEmitterLayer.emitterPosition = emitterPosition + self.staticEmitterLayer.setValue(emitterPosition, forKeyPath: "emitterBehaviors.attractor.position") + } +} + +private final class SliderStarsView: UIView { + private let emitterLayer = CAEmitterLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.emitterLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + private func setupEmitter() { + self.emitterLayer.emitterShape = .rectangle + self.emitterLayer.emitterMode = .surface + self.layer.addSublayer(self.emitterLayer) + + let emitter = CAEmitterCell() + emitter.name = "emitter" + emitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + emitter.birthRate = 20.0 + emitter.lifetime = 2.0 + emitter.velocity = 15.0 + emitter.velocityRange = 10 + emitter.scale = 0.15 + emitter.scaleRange = 0.08 + emitter.emissionRange = .pi / 4.0 + emitter.setValue(3.0, forKey: "mass") + emitter.setValue(2.0, forKey: "massRange") + self.emitterLayer.emitterCells = [emitter] + + let colors: [Any] = [ + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.38).cgColor, + UIColor.white.withAlphaComponent(0.38).cgColor, + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.38).cgColor, + UIColor.white.withAlphaComponent(0.38).cgColor, + UIColor.white.withAlphaComponent(0.0).cgColor + ] + let colorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + colorBehavior.setValue(colors, forKey: "colors") + emitter.setValue([colorBehavior], forKey: "emitterBehaviors") + } + + func update(size: CGSize, value: CGFloat) { + if self.emitterLayer.emitterCells == nil { + self.setupEmitter() + } + + self.emitterLayer.setValue(20.0 + Float(value * 200.0), forKeyPath: "emitterCells.emitter.birthRate") + self.emitterLayer.setValue(15.0 + value * 250.0, forKeyPath: "emitterCells.emitter.velocity") + + self.emitterLayer.frame = CGRect(origin: .zero, size: size) + self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0) + self.emitterLayer.emitterSize = size + } +} + +private final class AuctionStatComponent: Component { + let title: [AnimatedTextComponent.Item] + let subtitle: String + let theme: PresentationTheme + + init( + title: [AnimatedTextComponent.Item], + subtitle: String, + theme: PresentationTheme + ) { + self.title = title + self.subtitle = subtitle + self.theme = theme + } + + static func ==(lhs: AuctionStatComponent, rhs: AuctionStatComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.subtitle != rhs.subtitle { + return false + } + if lhs.theme != rhs.theme { + return false + } + return true + } + + final class View: UIView { + let background = ComponentView() + let title = ComponentView() + let subtitle = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AuctionStatComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let backgroundFrame = CGRect(origin: CGPoint(), size: availableSize) + let _ = self.background.update( + transition: transition, + component: AnyComponent(FilledRoundedRectangleComponent( + color: UIColor(rgb: 0x808084, alpha: 0.1), + cornerRadius: .value(12.0), + smoothCorners: true + )), + environment: {}, + containerSize: backgroundFrame.size + ) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let titleSize = self.title.update( + transition: .spring(duration: 0.2), + component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 20.0, weight: .semibold, traits: .monospacedNumbers), + color: component.theme.list.itemPrimaryTextColor, + items: component.title, + noDelay: true, + blur: true + )), + environment: {}, + containerSize: backgroundFrame.size + ) + + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.subtitle, font: Font.regular(11.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: backgroundFrame.size + ) + + let spacing: CGFloat = 2.0 + + let titleFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - titleSize.width) * 0.5), y: floor((backgroundFrame.height - titleSize.height - spacing - subtitleSize.height) * 0.5)), size: titleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - subtitleSize.width) * 0.5), y: titleFrame.maxY + spacing), size: subtitleSize) + + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + subtitleView.frame = subtitleFrame + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift index 88a12392aa..2067859721 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift @@ -14,23 +14,17 @@ import SheetComponent import MultilineTextComponent import MultilineTextWithEntitiesComponent import BundleIconComponent -import ButtonComponent import Markdown import BalancedTextComponent -import AvatarNode import TextFormat import TelegramStringFormatting import StarsAvatarComponent -import EmojiTextAttachmentView -import EmojiStatusComponent -import UndoUI import PlainButtonComponent import TooltipUI import GiftAnimationComponent -import LottieComponent import ContextUI -import TelegramNotices import GiftItemComponent +import GlassBarButtonComponent private final class GiftValueSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -179,7 +173,7 @@ private final class GiftValueSheetContent: CombinedComponent { } static var body: Body { - let buttons = Child(ButtonsComponent.self) + let closeButton = Child(GlassBarButtonComponent.self) let animation = Child(GiftCompositionComponent.self) let titleBackground = Child(RoundedRectangle.self) @@ -231,25 +225,7 @@ private final class GiftValueSheetContent: CombinedComponent { genericGift = gift } } - - let buttons = buttons.update( - component: ButtonsComponent( - theme: theme, - isOverlay: false, - showMoreButton: false, - closePressed: { [weak state] in - guard let state else { - return - } - state.dismiss(animated: true) - }, - morePressed: { _, _ in - } - ), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: context.transition - ) - + var originY: CGFloat = 0.0 let headerHeight: CGFloat = 210.0 @@ -655,8 +631,30 @@ private final class GiftValueSheetContent: CombinedComponent { originY += 12.0 } - context.add(buttons - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0)) + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak state] _ in + guard let state else { + return + } + state.dismiss(animated: true) + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom @@ -714,6 +712,7 @@ final class GiftValueSheetComponent: CombinedComponent { animateOut: animateOut, getController: controller )), + style: .glass, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, clipsContent: true, @@ -849,17 +848,11 @@ final class GiftValueScreen: ViewControllerComponentContainer { if let controller = controller as? TooltipScreen { controller.dismiss(inPlace: false) } - if let controller = controller as? UndoOverlayController { - controller.dismiss() - } }) self.forEachController({ controller in if let controller = controller as? TooltipScreen { controller.dismiss(inPlace: false) } - if let controller = controller as? UndoOverlayController { - controller.dismiss() - } return true }) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 7e04758d60..6c70365a8a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -368,6 +368,7 @@ private final class GiftViewSheetContent: CombinedComponent { deinit { self.disposable?.dispose() self.upgradePreviewDisposable.dispose() + self.upgradePreviewTimer?.invalidate() self.upgradeFormDisposable?.dispose() self.upgradeDisposable?.dispose() self.buyFormDisposable?.dispose() @@ -4555,7 +4556,7 @@ private final class GiftViewSheetContent: CombinedComponent { buttonAnimatedTitleItems.append( AnimatedTextComponent.Item( id: AnyHashable(buttonAnimatedTitleItems.count), - content: .icon("Item List/PremiumIcon", offset: CGPoint(x: 1.0, y: 2.0 + UIScreenPixel)) + content: .icon("Item List/PremiumIcon", tint: true, offset: CGPoint(x: 1.0, y: 2.0 + UIScreenPixel)) ) ) @@ -4607,7 +4608,6 @@ private final class GiftViewSheetContent: CombinedComponent { } else { buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))) } - let minutes = Int(upgradeTimeout / 60) let seconds = Int(upgradeTimeout % 60) diff --git a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ReorderingGestureRecognizer.swift b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ReorderingGestureRecognizer.swift index 911e5ef0f0..9b0dc88ca5 100644 --- a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ReorderingGestureRecognizer.swift +++ b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ReorderingGestureRecognizer.swift @@ -203,12 +203,12 @@ public final class ReorderGestureRecognizer: UIGestureRecognizer { } public func generateReorderingBackgroundImage(backgroundColor: UIColor) -> UIImage? { - return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 84.0, height: 84.0), contextGenerator: { size, context in context.clear(CGRect(origin: .zero, size: size)) - context.addPath(UIBezierPath(roundedRect: CGRect(x: 10, y: 10, width: 44, height: 44), cornerRadius: 10).cgPath) - context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 24.0, color: UIColor(white: 0.0, alpha: 0.35).cgColor) + context.addPath(UIBezierPath(roundedRect: CGRect(x: 16, y: 16, width: 52, height: 52), cornerRadius: 26).cgPath) + context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 36.0, color: UIColor(white: 0.0, alpha: 0.2).cgColor) context.setFillColor(backgroundColor.cgColor) context.fillPath() - })?.stretchableImage(withLeftCapWidth: 32, topCapHeight: 32) + })?.stretchableImage(withLeftCapWidth: 42, topCapHeight: 42) } diff --git a/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/Sources/ListItemSliderSelectorComponent.swift b/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/Sources/ListItemSliderSelectorComponent.swift index c7709e6a76..31cd8ad9a1 100644 --- a/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/Sources/ListItemSliderSelectorComponent.swift +++ b/submodules/TelegramUI/Components/ListItemSliderSelectorComponent/Sources/ListItemSliderSelectorComponent.swift @@ -95,13 +95,16 @@ public final class ListItemSliderSelectorComponent: Component { public let theme: PresentationTheme public let content: Content + public let preferNative: Bool public init( theme: PresentationTheme, - content: Content + content: Content, + preferNative: Bool = false ) { self.theme = theme self.content = content + self.preferNative = preferNative } public static func ==(lhs: ListItemSliderSelectorComponent, rhs: ListItemSliderSelectorComponent) -> Bool { @@ -111,6 +114,9 @@ public final class ListItemSliderSelectorComponent: Component { if lhs.content != rhs.content { return false } + if lhs.preferNative != rhs.preferNative { + return false + } return true } @@ -347,6 +353,7 @@ public final class ListItemSliderSelectorComponent: Component { discrete.selectedIndexUpdated(value) }) ), + useNative: component.preferNative, trackBackgroundColor: component.theme.list.controlSecondaryColor, trackForegroundColor: component.theme.list.itemAccentColor, minTrackForegroundColor: component.theme.list.itemAccentColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.6) @@ -368,6 +375,7 @@ public final class ListItemSliderSelectorComponent: Component { continuous.valueUpdated(value) }) ), + useNative: component.preferNative, trackBackgroundColor: component.theme.list.controlSecondaryColor, trackForegroundColor: component.theme.list.itemAccentColor, minTrackForegroundColor: component.theme.list.itemAccentColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.6) diff --git a/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift index e562bcbad8..0db1bf6728 100644 --- a/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift @@ -33,7 +33,9 @@ public final class ListTextFieldItemComponent: Component { public let placeholder: String public let autocapitalizationType: UITextAutocapitalizationType public let autocorrectionType: UITextAutocorrectionType + public let returnKeyType: UIReturnKeyType public let updated: ((String) -> Void)? + public let onReturn: (() -> Void)? public let tag: AnyObject? public init( @@ -44,7 +46,9 @@ public final class ListTextFieldItemComponent: Component { placeholder: String, autocapitalizationType: UITextAutocapitalizationType = .sentences, autocorrectionType: UITextAutocorrectionType = .default, + returnKeyType: UIReturnKeyType = .default, updated: ((String) -> Void)?, + onReturn: (() -> Void)? = nil, tag: AnyObject? = nil ) { self.style = style @@ -54,7 +58,9 @@ public final class ListTextFieldItemComponent: Component { self.placeholder = placeholder self.autocapitalizationType = autocapitalizationType self.autocorrectionType = autocorrectionType + self.returnKeyType = returnKeyType self.updated = updated + self.onReturn = onReturn self.tag = tag } @@ -80,6 +86,9 @@ public final class ListTextFieldItemComponent: Component { if lhs.autocorrectionType != rhs.autocorrectionType { return false } + if lhs.returnKeyType != rhs.returnKeyType { + return false + } if (lhs.updated == nil) != (rhs.updated == nil) { return false } @@ -125,6 +134,7 @@ public final class ListTextFieldItemComponent: Component { } public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.component?.onReturn?() return true } @@ -159,6 +169,14 @@ public final class ListTextFieldItemComponent: Component { self.textField.becomeFirstResponder() } + public func textFieldDidBeginEditing(_ textField: UITextField) { + self.clearButton.view?.isHidden = false + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + self.clearButton.view?.isHidden = true + } + func update(component: ListTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -187,6 +205,9 @@ public final class ListTextFieldItemComponent: Component { if self.textField.autocorrectionType != component.autocorrectionType { self.textField.autocorrectionType = component.autocorrectionType } + if self.textField.returnKeyType != component.returnKeyType { + self.textField.returnKeyType = component.returnKeyType + } let themeUpdated = component.theme !== previousComponent?.theme @@ -253,7 +274,7 @@ public final class ListTextFieldItemComponent: Component { self.addSubview(clearButtonView) } transition.setFrame(view: clearButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 0.0 - clearButtonSize.width, y: floor((contentHeight - clearButtonSize.height) * 0.5)), size: clearButtonSize)) - clearButtonView.isHidden = self.currentText.isEmpty + clearButtonView.isHidden = self.currentText.isEmpty || !self.textField.isFirstResponder } self.separatorInset = 16.0 diff --git a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal index fd71d03e68..e254e1f707 100644 --- a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal +++ b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal @@ -58,6 +58,5 @@ fragment float4 bt709ToRGBFragmentShader(RasterizerData in [[stage_in]], float4 pixel = BT709Decode(Y, Cb, Cr); pixel = sRGBGammaDecode(pixel); - //pixel.rgb = pow(pixel.rgb, 1.0 / 2.2); return pixel; } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index be27ec04bf..602656dc59 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -58,8 +58,8 @@ private func rectangleMaskImage(size: CGSize) -> CIImage { return CIImage(cgImage: image!) } -final class MediaEditorComposer { - enum Input { +public final class MediaEditorComposer { + public enum Input { case texture(MTLTexture, CMTime, Bool, CGRect?, CGFloat, CGPoint) case videoBuffer(VideoPixelBuffer, CGRect?, CGFloat, CGPoint) case ciImage(CIImage, CMTime) @@ -105,8 +105,8 @@ final class MediaEditorComposer { private var maskImage: CIImage? - init( - postbox: Postbox, + public init( + postbox: Postbox?, values: MediaEditorValues, dimensions: CGSize, outputDimensions: CGSize, @@ -137,8 +137,10 @@ final class MediaEditorComposer { } var entities: [MediaEditorComposerEntity] = [] - for entity in values.entities { - entities.append(contentsOf: composerEntitiesForDrawingEntity(postbox: postbox, textScale: textScale, entity: entity.entity, colorSpace: colorSpace)) + if let postbox { + for entity in values.entities { + entities.append(contentsOf: composerEntitiesForDrawingEntity(postbox: postbox, textScale: textScale, entity: entity.entity, colorSpace: colorSpace)) + } } self.entities = entities @@ -161,7 +163,7 @@ final class MediaEditorComposer { } var previousAdditionalInput: [Int: Input] = [:] - func process(main: Input, additional: [Input?], timestamp: CMTime, pool: CVPixelBufferPool?, completion: @escaping (CVPixelBuffer?) -> Void) { + public func process(main: Input, additional: [Input?], timestamp: CMTime, pool: CVPixelBufferPool?, completion: @escaping (CVPixelBuffer?) -> Void) { guard let pool, let ciContext = self.ciContext else { completion(nil) return @@ -254,7 +256,9 @@ public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inp return } } - completion(nil) + Queue.mainQueue().async { + completion(nil) + } }) } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift index 9595d22207..976eab7acf 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift @@ -5,12 +5,12 @@ import MetalKit import Photos import SwiftSignalKit -final class VideoPixelBuffer { +public final class VideoPixelBuffer { let pixelBuffer: CVPixelBuffer let rotation: TextureRotation let timestamp: CMTime - init( + public init( pixelBuffer: CVPixelBuffer, rotation: TextureRotation, timestamp: CMTime diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift index 54a849f09e..d96f39ef34 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift @@ -9,7 +9,7 @@ struct VertexData { let localPos: simd_float2 } -enum TextureRotation: Int { +public enum TextureRotation: Int { case rotate0Degrees case rotate0DegreesMirrored case rotate90Degrees diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index 6db29e5792..82dfe3588d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -250,7 +250,6 @@ private class MessagePriceItemNode: ListViewItemNode { self.addSubnode(self.leftTextNode) self.addSubnode(self.rightTextNode) self.addSubnode(self.centerTextButtonNode) - self.centerTextButtonNode.view.addSubview(self.centerTextButtonBackground) self.centerTextButtonNode.addSubnode(self.centerLeftTextNode) self.centerTextButtonNode.addSubnode(self.centerRightTextNode) self.addSubnode(self.lockIconNode) @@ -285,6 +284,8 @@ private class MessagePriceItemNode: ListViewItemNode { self.view.addSubview(sliderView) sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) self.sliderView = sliderView + + self.centerTextButtonNode.view.insertSubview(self.centerTextButtonBackground, at: 0) } @objc private func centerTextButtonPressed() { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 610273851e..c47c21c951 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -7890,7 +7890,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } - self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create)) + self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create(liveStream: false))) } private func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift index 53daccb590..66566d8e7a 100644 --- a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift @@ -29,6 +29,7 @@ public final class SearchInputPanelComponent: Component { public let theme: PresentationTheme public let strings: PresentationStrings public let metrics: LayoutMetrics + public let safeInsets: UIEdgeInsets public let placeholder: String? public let resetText: ResetText? public let updated: ((String) -> Void) @@ -38,6 +39,7 @@ public final class SearchInputPanelComponent: Component { theme: PresentationTheme, strings: PresentationStrings, metrics: LayoutMetrics, + safeInsets: UIEdgeInsets, placeholder: String? = nil, resetText: ResetText? = nil, updated: @escaping ((String) -> Void), @@ -46,6 +48,7 @@ public final class SearchInputPanelComponent: Component { self.theme = theme self.strings = strings self.metrics = metrics + self.safeInsets = safeInsets self.placeholder = placeholder self.resetText = resetText self.updated = updated @@ -62,6 +65,9 @@ public final class SearchInputPanelComponent: Component { if lhs.metrics != rhs.metrics { return false } + if lhs.safeInsets != rhs.safeInsets { + return false + } if lhs.placeholder != rhs.placeholder { return false } @@ -189,7 +195,7 @@ public final class SearchInputPanelComponent: Component { let backgroundColor = component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.75) - var edgeInsets = UIEdgeInsets(top: 10.0, left: 11.0, bottom: 10.0, right: 11.0) + var edgeInsets = UIEdgeInsets(top: 10.0, left: 11.0 + component.safeInsets.left, bottom: 10.0, right: 11.0 + component.safeInsets.right) if case .regular = component.metrics.widthClass { edgeInsets.bottom += 18.0 } diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD index 8b6e81b7c6..944da5cc75 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD @@ -25,7 +25,6 @@ swift_library( "//submodules/AppBundle", "//submodules/TelegramStringFormatting", "//submodules/PresentationDataUtils", - "//submodules/Components/SolidRoundedButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/AnimatedCounterComponent", @@ -42,11 +41,16 @@ swift_library( "//submodules/UndoUI", "//submodules/TemporaryCachedPeerDataManager", "//submodules/CountrySelectionUI", + "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ListItemSliderSelectorComponent", "//submodules/ContextUI", "//submodules/PromptUI", "//submodules/DirectMediaImageCache", "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TextFormat", + "//submodules/PeerInfoUI/CreateExternalMediaStreamScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift index 644c55a512..22aa853825 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift @@ -173,7 +173,7 @@ final class CategoryListItemComponent: Component { let contextInset: CGFloat = 0.0 - let height: CGFloat = 56.0 + let height: CGFloat = 64.0 let verticalInset: CGFloat = 0.0 var leftInset: CGFloat = 62.0 let rightInset: CGFloat = contextInset * 2.0 + 8.0 @@ -264,7 +264,7 @@ final class CategoryListItemComponent: Component { containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) ) - let titleSpacing: CGFloat = 1.0 + let titleSpacing: CGFloat = 3.0 var centralContentHeight: CGFloat = titleSize.height if !labelData.0.isEmpty { centralContentHeight += titleSpacing + labelSize.height diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift new file mode 100644 index 0000000000..87d8462063 --- /dev/null +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift @@ -0,0 +1,1421 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import ViewControllerComponent +import ComponentDisplayAdapters +import TelegramPresentationData +import AccountContext +import TelegramCore +import Postbox +import MultilineTextComponent +import PresentationDataUtils +import ButtonComponent +import TokenListTextField +import AvatarNode +import LocalizedPeerData +import PeerListItemComponent +import LottieComponent +import TooltipUI +import Markdown +import TelegramStringFormatting +import ListSectionComponent +import ListActionItemComponent +import BundleIconComponent +import GlassBarButtonComponent +import EdgeEffect +import TextFormat +import ListItemSliderSelectorComponent +import CreateExternalMediaStreamScreen + +final class LiveStreamSettingsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let stateContext: LiveStreamSettingsScreen.StateContext + let editCategory: (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void + let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void + let completion: (LiveStreamSettingsScreen.Result) -> Void + + init( + context: AccountContext, + stateContext: LiveStreamSettingsScreen.StateContext, + editCategory: @escaping (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void, + editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void, + completion: @escaping (LiveStreamSettingsScreen.Result) -> Void + ) { + self.context = context + self.stateContext = stateContext + self.editCategory = editCategory + self.editBlockedPeers = editBlockedPeers + self.completion = completion + } + + static func ==(lhs: LiveStreamSettingsScreenComponent, rhs: LiveStreamSettingsScreenComponent) -> Bool { + return true + } + + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + final class View: UIView, UIScrollViewDelegate { + private let scrollView: ScrollView + private let topEdgeEffectView: EdgeEffectView + private let bottomEdgeEffectView: EdgeEffectView + + private let streamAsSection = ComponentView() + private let privacySection = ComponentView() + private let externalStreamSection = ComponentView() + private let settingsSection = ComponentView() + private let paidMessageSection = ComponentView() + + private let title = ComponentView() + private let cancelButton = ComponentView() + private let doneButton = ComponentView() + private let actionButton = ComponentView() + + private var isUpdating: Bool = false + private var ignoreScrolling: Bool = false + + private var component: LiveStreamSettingsScreenComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + self.scrollView = ScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.alwaysBounceVertical = true + + self.topEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView = EdgeEffectView() + + super.init(frame: frame) + + self.scrollView.delegate = self + self.addSubview(self.scrollView) + + self.addSubview(self.topEdgeEffectView) + self.addSubview(self.bottomEdgeEffectView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + + } + + func scrollToTop() { + self.scrollView.setContentOffset(CGPoint(), animated: true) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.endEditing(true) + } + + private func updateScrolling(transition: ComponentTransition) { + + } + + fileprivate var credentialsPromise = Promise() + private func presentCreateExternalStream() { + guard let component = self.component, let controller = self.environment?.controller() as? LiveStreamSettingsScreen else { + return + } + var dismissImpl: (() -> Void)? + let streamController = CreateExternalMediaStreamScreen( + context: component.context, + peerId: component.stateContext.sendAsPeerId ?? component.context.account.peerId, + credentialsPromise: self.credentialsPromise, + mode: .create(liveStream: true), + completion: { [weak self] in + guard let self else { + return + } + self.complete(rtmp: true) + dismissImpl?() + } + ) + dismissImpl = { [weak controller] in + guard let controller, let navigationController = controller.navigationController as? NavigationController else { + return + } + var controllers = navigationController.viewControllers + controllers = controllers.filter { c in + return !(c is LiveStreamSettingsScreen || c is CreateExternalMediaStreamScreen) + } + navigationController.setViewControllers(controllers, animated: true) + } + controller.push(streamController) + } + + private func presentStreamAsPeer() { + guard let component = self.component else { + return + } + let stateContext = ShareWithPeersScreen.StateContext( + context: component.context, + subject: .peers(peers: component.stateContext.stateValue?.sendAsPeers ?? [], peerId: component.stateContext.sendAsPeerId), + liveStream: true, + editing: false + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + let peersController = ShareWithPeersScreen( + context: component.context, + initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), + stateContext: stateContext, + completion: { _, _, _, _, _, _, _ in }, + editCategory: { _, _, _, _ in }, + editBlockedPeers: { _, _, _, _ in }, + peerCompletion: { [weak self] peerId in + guard let self else { + return + } + + self.credentialsPromise.set(component.context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: true, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) + + component.stateContext.sendAsPeerId = peerId + self.state?.updated(transition: .spring(duration: 0.4)) + } + ) + if let controller = self.environment?.controller() as? LiveStreamSettingsScreen { + controller.dismissAllTooltips() + controller.push(peersController) + } + }) + } + + private func complete(rtmp: Bool) { + guard let component = self.component else { + return + } + component.completion( + LiveStreamSettingsScreen.Result( + sendAsPeerId: component.stateContext.sendAsPeerId, + privacy: component.stateContext.privacy, + allowComments: component.stateContext.allowComments, + isForwardingDisabled: component.stateContext.isForwardingDisabled, + pin: component.stateContext.pin, + paidMessageStars: component.stateContext.paidMessageStars, + startRtmpStream: rtmp + ) + ) + } + + func update(component: LiveStreamSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + var alphaTransition = transition + if !transition.animation.isImmediate { + alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut)) + } + + let environment = environment[EnvironmentType.self].value + let themeUpdated = self.environment?.theme !== environment.theme + self.environment = environment + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let theme = environment.theme.withModalBlocksBackground() + + guard let screenState = component.stateContext.stateValue else { + return CGSize() + } + + if self.component == nil { + self.credentialsPromise.set(component.context.engine.calls.getGroupCallStreamCredentials(peerId: screenState.sendAsPeerId ?? component.context.account.peerId, isLiveStream: true, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) + } + + self.component = component + self.state = state + + let topInset: CGFloat = 24.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let sectionSpacing: CGFloat = 32.0 + + if themeUpdated { + self.backgroundColor = theme.list.blocksBackgroundColor + } + + let footerTextFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize) + let footerBoldTextFont = Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize) + let footerTextColor = theme.list.freeTextColor + + var contentHeight: CGFloat = 0.0 + contentHeight += environment.navigationHeight + contentHeight += topInset + + let effectiveSendAsPeerId = screenState.sendAsPeerId ?? component.context.account.peerId + if screenState.sendAsPeers.count > 1, let peer = screenState.sendAsPeers.first(where: { $0.id == effectiveSendAsPeerId }) { + let subtitle: String? + if case .user = peer { + subtitle = environment.strings.VoiceChat_PersonalAccount + } else { + if case let .channel(channel) = peer { + if case .broadcast = channel.info { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusSubscribers(Int32(max(1, count))) + } else { + subtitle = environment.strings.Channel_Status + } + } else { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusMembers(Int32(max(1, count))) + } else { + subtitle = environment.strings.Group_Status + } + } + } else { + subtitle = nil + } + } + + let streamAsSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent( + PeerListItemComponent( + context: component.context, + theme: theme, + strings: environment.strings, + style: .generic, + sideInset: 0.0, + title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), + peer: peer, + subtitle: subtitle.flatMap { PeerListItemComponent.Subtitle(text: $0, color: .neutral) }, + subtitleAccessory: .none, + presence: nil, + rightAccessory: .disclosure, + selectionState: .none, + hasNext: false, + action: { [weak self] _, _, _ in + guard let self else { + return + } + self.presentStreamAsPeer() + } + ) + ))] + + let streamAsSectionSize = self.streamAsSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: streamAsSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let streamAsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: streamAsSectionSize) + if let streamAsSectionView = self.streamAsSection.view as? ListSectionComponent.View { + if streamAsSectionView.superview == nil { + self.scrollView.addSubview(streamAsSectionView) + self.streamAsSection.parentState = state + } + transition.setFrame(view: streamAsSectionView, frame: streamAsSectionFrame) + } + contentHeight += streamAsSectionSize.height + contentHeight += sectionSpacing + } + + if screenState.sendAsPeerId?.namespace != Namespaces.Peer.CloudChannel { + var privacySectionItems: [AnyComponentWithIdentity] = [] + + var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] + + var everyoneSubtitle = environment.strings.Story_Privacy_ExcludePeople + if (screenState.savedSelectedPeers[.everyone]?.count ?? 0) > 0 { + var peerNamesArray: [String] = [] + var peersCount = 0 + if let peerIds = screenState.savedSelectedPeers[.everyone] { + peersCount = peerIds.count + for peerId in peerIds { + if let peer = screenState.peersMap[peerId] { + peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)) + } + } + } + let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", ")) + if peersCount == 1 { + if !peerNames.isEmpty { + everyoneSubtitle = environment.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string + } else { + everyoneSubtitle = environment.strings.Story_Privacy_ExcludePeopleExcept(1) + } + } else { + if !peerNames.isEmpty { + everyoneSubtitle = environment.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string + } else { + everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(peersCount)) + } + } + } + categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( + id: .everyone, + title: environment.strings.Story_Privacy_CategoryEveryone, + icon: "Media Editor/Privacy/Everyone", + iconColor: .blue, + actionTitle: everyoneSubtitle + )) + + var contactsSubtitle = environment.strings.Story_Privacy_ExcludePeople + if (screenState.savedSelectedPeers[.contacts]?.count ?? 0) > 0 { + var peerNamesArray: [String] = [] + var peersCount = 0 + if let peerIds = screenState.savedSelectedPeers[.contacts] { + peersCount = peerIds.count + for peerId in peerIds { + if let peer = screenState.peersMap[peerId] { + peerNamesArray.append(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)) + } + } + } + let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", ")) + if peersCount == 1 { + if !peerNames.isEmpty { + contactsSubtitle = environment.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string + } else { + contactsSubtitle = environment.strings.Story_Privacy_ExcludePeopleExcept(1) + } + } else { + if !peerNames.isEmpty { + contactsSubtitle = environment.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string + } else { + contactsSubtitle = environment.strings.Story_Privacy_ExcludePeopleExcept(Int32(peersCount)) + } + } + } + categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( + id: .contacts, + title: environment.strings.Story_Privacy_CategoryContacts, + icon: "Media Editor/Privacy/Contacts", + iconColor: .violet, + actionTitle: contactsSubtitle + )) + + var closeFriendsSubtitle = environment.strings.Story_Privacy_EditList + if !screenState.closeFriendsPeers.isEmpty { + if screenState.closeFriendsPeers.count > 2 { + closeFriendsSubtitle = environment.strings.Story_Privacy_People(Int32(screenState.closeFriendsPeers.count)) + } else { + closeFriendsSubtitle = String(screenState.closeFriendsPeers.map { $0.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", ")) + } + } + categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( + id: .closeFriends, + title: environment.strings.Story_Privacy_CategoryCloseFriends, + icon: "Media Editor/Privacy/CloseFriends", + iconColor: .green, + actionTitle: closeFriendsSubtitle + )) + + var selectedContactsSubtitle = environment.strings.Story_Privacy_Choose + if (screenState.savedSelectedPeers[.nobody]?.count ?? 0) > 0 { + var peerNamesArray: [String] = [] + var peersCount = 0 + if let peerIds = screenState.savedSelectedPeers[.nobody] { + peersCount = peerIds.count + for peerId in peerIds { + if let peer = screenState.peersMap[peerId] { + peerNamesArray.append(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)) + } + } + } + let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", ")) + if peersCount == 1 { + if !peerNames.isEmpty { + selectedContactsSubtitle = peerNames + } else { + selectedContactsSubtitle = environment.strings.Story_Privacy_People(1) + } + } else { + if !peerNames.isEmpty { + selectedContactsSubtitle = peerNames + } else { + selectedContactsSubtitle = environment.strings.Story_Privacy_People(Int32(peersCount)) + } + } + } + categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( + id: .selectedContacts, + title: environment.strings.Story_Privacy_CategorySelectedContacts, + icon: "Media Editor/Privacy/SelectedUsers", + iconColor: .yellow, + actionTitle: selectedContactsSubtitle + )) + + + for i in 0 ..< categoryItems.count { + let item = categoryItems[i] + + var isSelected = false + switch screenState.privacy.base { + case .everyone: + isSelected = item.id == .everyone + case .contacts: + isSelected = item.id == .contacts + case .closeFriends: + isSelected = item.id == .closeFriends + case .nobody: + isSelected = item.id == .selectedContacts + } + + privacySectionItems.append(AnyComponentWithIdentity(id: item.id, component: AnyComponent( + CategoryListItemComponent( + context: component.context, + theme: theme, + title: item.title, + color: item.iconColor, + iconName: item.icon, + subtitle: item.actionTitle, + selectionState: .editing(isSelected:isSelected, isTinted: false), + hasNext: i != categoryItems.count - 1, + action: { [weak self] in + guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() as? LiveStreamSettingsScreen else { + return + } + if isSelected { + } else { + let base: EngineStoryPrivacy.Base + switch item.id { + case .everyone: + base = .everyone + case .contacts: + base = .contacts + case .closeFriends: + base = .closeFriends + case .selectedContacts: + base = .nobody + } + let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? [] + + component.stateContext.privacy = EngineStoryPrivacy(base: base, additionallyIncludePeers: selectedPeers) + + let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? [] + if item.id == .selectedContacts && selectedPeers.isEmpty { + component.editCategory( + EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), + screenState.allowComments, + screenState.isForwardingDisabled, + screenState.pin, + screenState.paidMessageStars + ) + controller.dismissAllTooltips() + controller.dismiss() + } else if item.id == .closeFriends && closeFriends.isEmpty { + component.editCategory( + EngineStoryPrivacy(base: .closeFriends, additionallyIncludePeers: []), + screenState.allowComments, + screenState.isForwardingDisabled, + screenState.pin, + screenState.paidMessageStars + ) + controller.dismissAllTooltips() + controller.dismiss() + } + } + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.35, curve: .spring))) + }, + secondaryAction: { [weak self] in + guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() as? LiveStreamSettingsScreen else { + return + } + let base: EngineStoryPrivacy.Base + switch item.id { + case .everyone: + base = .everyone + case .contacts: + base = .contacts + case .closeFriends: + base = .closeFriends + case .selectedContacts: + base = .nobody + } + let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? [] + + component.editCategory( + EngineStoryPrivacy(base: base, additionallyIncludePeers: selectedPeers), + screenState.allowComments, + screenState.isForwardingDisabled, + screenState.pin, + screenState.paidMessageStars + ) + controller.dismissAllTooltips() + controller.dismiss() + } + ) + ))) + } + + let privacySectionHeader = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "WHO CAN VIEW THIS LIVE", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )) + + let privacySectionFooter = AnyComponent(MultilineTextComponent( + text: .markdown( + text: "[Select people]() who won't see your live.", + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: footerTextFont, textColor: footerTextColor), + bold: MarkdownAttributeSet(font: footerBoldTextFont, textColor: footerTextColor), + link: MarkdownAttributeSet(font: footerTextFont, textColor: theme.list.itemAccentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + ), + maximumNumberOfLines: 0, + highlightColor: presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() as? LiveStreamSettingsScreen else { + return + } + component.editBlockedPeers( + component.stateContext.privacy, + screenState.allowComments, + screenState.isForwardingDisabled, + screenState.pin, + screenState.paidMessageStars + ) + controller.dismissAllTooltips() + controller.dismiss() + } + )) + + var privacySectionTransition = transition + if self.privacySection.view == nil { + privacySectionTransition = .immediate + } + let privacySectionSize = self.privacySection.update( + transition: privacySectionTransition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: privacySectionHeader, + footer: privacySectionFooter, + items: privacySectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let privacySectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: privacySectionSize) + if let privacySectionView = self.privacySection.view as? ListSectionComponent.View { + if privacySectionView.superview == nil { + self.scrollView.addSubview(privacySectionView) + self.privacySection.parentState = state + + privacySectionView.alpha = 1.0 + transition.animateAlpha(view: privacySectionView, from: 0.0, to: 1.0) + } + privacySectionTransition.setFrame(view: privacySectionView, frame: privacySectionFrame) + } + contentHeight += privacySectionSize.height + contentHeight += sectionSpacing + } else if let privacySectionView = self.privacySection.view as? ListSectionComponent.View { + transition.setAlpha(view: privacySectionView, alpha: 0.0, completion: { _ in + privacySectionView.removeFromSuperview() + }) + } + + if !screenState.isEdit { + let externalStreamSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent( + ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Connect Stream", font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: theme.list.itemPrimaryTextColor)))), + action: { [weak self] _ in + guard let self else { + return + } + self.presentCreateExternalStream() + } + ) + ))] + let externalStreamFooterComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Stream with a different app.", font: footerTextFont, textColor: footerTextColor)), + maximumNumberOfLines: 0 + )) + + let externalStreamSectionSize = self.externalStreamSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: externalStreamFooterComponent, + items: externalStreamSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let externalStreamSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: externalStreamSectionSize) + if let externalStreamSectionView = self.externalStreamSection.view as? ListSectionComponent.View { + if externalStreamSectionView.superview == nil { + self.scrollView.addSubview(externalStreamSectionView) + self.externalStreamSection.parentState = state + } + transition.setFrame(view: externalStreamSectionView, frame: externalStreamSectionFrame) + } + contentHeight += externalStreamSectionSize.height + contentHeight += sectionSpacing + } + + + var settingsSectionItems: [AnyComponentWithIdentity] = [] + settingsSectionItems.append(AnyComponentWithIdentity(id: "comments", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Allow Comments", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: screenState.allowComments, action: { [weak self] _ in + guard let self else { + return + } + component.stateContext.allowComments = !component.stateContext.allowComments + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + )))) + + settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Allow Screenshots", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: !screenState.isForwardingDisabled, action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + component.stateContext.isForwardingDisabled = !component.stateContext.isForwardingDisabled + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + )))) + + var pinTitle = "Post to My Profile" + var pinInfo = "Keep this live on your profile." + if screenState.sendAsPeerId?.namespace == Namespaces.Peer.CloudChannel { + pinTitle = "Post to Channel Profile" + pinInfo = "Keep this live on channel profile." + } + + settingsSectionItems.append(AnyComponentWithIdentity(id: "pin", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: pinTitle, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: screenState.pin, action: { [weak self] _ in + guard let self else { + return + } + component.stateContext.pin = !component.stateContext.pin + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + )))) + + let settingsSectionFooterComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: pinInfo, font: footerTextFont, textColor: footerTextColor)), + maximumNumberOfLines: 0 + )) + + let settingsSectionSize = self.settingsSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: settingsSectionFooterComponent, + items: settingsSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let settingsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: settingsSectionSize) + if let settingsSectionView = self.settingsSection.view { + if settingsSectionView.superview == nil { + self.scrollView.addSubview(settingsSectionView) + self.settingsSection.parentState = state + } + transition.setFrame(view: settingsSectionView, frame: settingsSectionFrame) + } + contentHeight += settingsSectionSize.height + contentHeight += sectionSpacing + + let paidMessageSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent( + ListItemSliderSelectorComponent( + theme: theme, + content: .continuous(ListItemSliderSelectorComponent.Continuous( + value: Double(screenState.paidMessageStars) / Double(screenState.maxPaidMessageStars), + minValue: 0, + lowerBoundTitle: "0", + upperBoundTitle: "\(presentationStringsFormattedNumber(Int32(clamping: screenState.maxPaidMessageStars), environment.dateTimeFormat.groupingSeparator))", + title: "\(screenState.paidMessageStars) Stars", + valueUpdated: { [weak self] value in + guard let self, let component = self.component else { + return + } + component.stateContext.paidMessageStars = Int64(Double(screenState.maxPaidMessageStars) * value) + self.state?.updated(transition: .immediate) + } + )), + preferNative: true + ) + ))] + + let paidMessageSectionHeader = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "PRICE PER COMMENT", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )) + + let paidMessageSectionFooterComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "The price a viewer must pay to send a comment.", font: footerTextFont, textColor: footerTextColor)), + maximumNumberOfLines: 0 + )) + + let paidMessageSectionSize = self.paidMessageSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: paidMessageSectionHeader, + footer: paidMessageSectionFooterComponent, + items: paidMessageSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let paidMessageSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: paidMessageSectionSize) + if let paidMessageSectionView = self.paidMessageSection.view as? ListSectionComponent.View { + if paidMessageSectionView.superview == nil { + self.scrollView.addSubview(paidMessageSectionView) + self.paidMessageSection.parentState = state + } + transition.setFrame(view: paidMessageSectionView, frame: paidMessageSectionFrame) + } + contentHeight += paidMessageSectionSize.height + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) + self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + + let bottomEdgeEffectHeight: CGFloat = 96.0 + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomEdgeEffectHeight), size: CGSize(width: availableSize.width, height: bottomEdgeEffectHeight)) + transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + + let title: String = screenState.isEdit ? "Live Stream" : "Live Settings" + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: title, + font: Font.semibold(17.0), + textColor: environment.theme.rootController.navigationBar.primaryTextColor + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? LiveStreamSettingsScreen else { + return + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + if screenState.isEdit { + let doneButtonSize = self.doneButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.list.itemCheckColors.fillColor, + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + isEnabled: true, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? LiveStreamSettingsScreen else { + return + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.addSubview(doneButtonView) + } + transition.setFrame(view: doneButtonView, frame: doneButtonFrame) + } + + contentHeight += environment.safeInsets.bottom + } else { + let actionButtonSize = self.actionButton.update( + transition: transition, + component: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: "label", + component: AnyComponent(ButtonTextContentComponent( + text: "Save Settings", + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor, + combinedAlignment: true + )) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self, let controller = self.environment?.controller() as? LiveStreamSettingsScreen else { + return + } + self.complete(rtmp: false) + controller.dismiss() + } + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: 52.0) + ) + + let bottomPanelHeight = 10.0 + environment.safeInsets.bottom + actionButtonSize.height + let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - actionButtonSize.width) / 2.0), y: availableSize.height - bottomPanelHeight), size: actionButtonSize) + if let actionButtonView = self.actionButton.view { + if actionButtonView.superview == nil { + self.addSubview(actionButtonView) + } + transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + } + + contentHeight += bottomPanelHeight + sectionSpacing + } + + self.ignoreScrolling = true + let previousBounds = self.scrollView.bounds + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0) + if self.scrollView.verticalScrollIndicatorInsets != scrollInsets { + self.scrollView.verticalScrollIndicatorInsets = scrollInsets + } + + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class LiveStreamSettingsScreen: ViewControllerComponentContainer { + public enum Mode { + case create(sendAsPeerId: EnginePeer.Id?, privacy: EngineStoryPrivacy, allowComments: Bool, isForwardingDisabled: Bool, pin: Bool, paidMessageStars: Int64) + case edit(PresentationGroupCall) + } + + public struct Result { + public var sendAsPeerId: EnginePeer.Id? + public var privacy: EngineStoryPrivacy + public var allowComments: Bool + public var isForwardingDisabled: Bool + public var pin: Bool + public var paidMessageStars: Int64 + public var startRtmpStream: Bool + } + + public init( + context: AccountContext, + stateContext: StateContext, + editCategory: @escaping (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void, + editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool, Bool, Int64) -> Void, + completion: @escaping (Result) -> Void + ) { + super.init(context: context, component: LiveStreamSettingsScreenComponent( + context: context, + stateContext: stateContext, + editCategory: editCategory, + editBlockedPeers: editBlockedPeers, + completion: completion + ), navigationBarAppearance: .transparent, theme: .dark) + + self.navigationPresentation = .modal + self._hasGlassStyle = true + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? LiveStreamSettingsScreenComponent.View else { + return + } + componentView.scrollToTop() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + fileprivate func dismissAllTooltips() { + self.window?.forEachController { controller in + if let controller = controller as? TooltipScreen { + controller.dismiss() + } + } + self.forEachController { controller in + if let controller = controller as? TooltipScreen { + controller.dismiss() + } + return true + } + } + + final class State { + var isEdit: Bool + var maxPaidMessageStars: Int64 + var sendAsPeerId: EnginePeer.Id? + var privacy: EngineStoryPrivacy + var allowComments: Bool + var isForwardingDisabled: Bool + var pin: Bool + var paidMessageStars: Int64 + var sendAsPeers: [EnginePeer] + var peersMap: [EnginePeer.Id: EnginePeer] + var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] + var participants: [EnginePeer.Id: Int] + var closeFriendsPeers: [EnginePeer] + var grayListPeers: [EnginePeer] + + init( + isEdit: Bool, + maxPaidMessageStars: Int64, + sendAsPeerId: EnginePeer.Id?, + privacy: EngineStoryPrivacy, + allowComments: Bool, + isForwardingDisabled: Bool, + pin: Bool, + paidMessageStars: Int64, + sendAsPeers: [EnginePeer], + peersMap: [EnginePeer.Id: EnginePeer], + savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]], + participants: [EnginePeer.Id: Int], + closeFriendsPeers: [EnginePeer], + grayListPeers: [EnginePeer] + ) { + self.isEdit = isEdit + self.maxPaidMessageStars = maxPaidMessageStars + self.sendAsPeerId = sendAsPeerId + self.privacy = privacy + self.allowComments = allowComments + self.isForwardingDisabled = isForwardingDisabled + self.pin = pin + self.paidMessageStars = paidMessageStars + self.sendAsPeers = sendAsPeers + self.peersMap = peersMap + self.savedSelectedPeers = savedSelectedPeers + self.participants = participants + self.closeFriendsPeers = closeFriendsPeers + self.grayListPeers = grayListPeers + } + } + + public final class StateContext { + let blockedPeersContext: BlockedPeersContext? + + var stateValue: State? + private let statePromise = Promise() + var state: Signal { + return self.statePromise.get() + } + private var stateDisposable: Disposable? + + private let readyPromise = ValuePromise(false, ignoreRepeated: true) + public var ready: Signal { + return self.readyPromise.get() + } + + var sendAsPeerId: EnginePeer.Id? { + get { + return self.stateValue?.sendAsPeerId + } + set(value) { + self.stateValue?.sendAsPeerId = value + } + } + var privacy: EngineStoryPrivacy { + get { + return self.stateValue?.privacy ?? EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []) + } + set(value) { + self.stateValue?.privacy = value + } + } + var allowComments: Bool { + get { + return self.stateValue?.allowComments ?? true + } + set(value) { + self.stateValue?.allowComments = value + } + } + var isForwardingDisabled: Bool { + get { + return self.stateValue?.isForwardingDisabled ?? false + } + set(value) { + self.stateValue?.isForwardingDisabled = value + } + } + var pin: Bool { + get { + return self.stateValue?.pin ?? true + } + set(value) { + self.stateValue?.pin = value + } + } + var paidMessageStars: Int64 { + get { + return self.stateValue?.paidMessageStars ?? 0 + } + set(value) { + self.stateValue?.paidMessageStars = value + } + } + + public init( + context: AccountContext, + mode: LiveStreamSettingsScreen.Mode, + initialSelectedPeers: [EngineStoryPrivacy.Base: [EnginePeer.Id]] = [:], + closeFriends: Signal<[EnginePeer], NoError>, + adminedChannels: Signal<[EnginePeer], NoError>, + blockedPeersContext: BlockedPeersContext? + ) { + self.blockedPeersContext = blockedPeersContext + + let grayListPeers: Signal<[EnginePeer], NoError> + if let blockedPeersContext { + grayListPeers = blockedPeersContext.state + |> map { state -> [EnginePeer] in + return state.peers.compactMap { $0.peer.flatMap(EnginePeer.init) } + } + } else { + grayListPeers = .single([]) + } + + let savedEveryoneExceptionPeers = peersListStoredState(engine: context.engine, base: .everyone) + let savedContactsExceptionPeers = peersListStoredState(engine: context.engine, base: .contacts) + let savedSelectedPeers = peersListStoredState(engine: context.engine, base: .nobody) + + let savedPeers = combineLatest( + savedEveryoneExceptionPeers, + savedContactsExceptionPeers, + savedSelectedPeers + ) |> mapToSignal { everyone, contacts, selected -> Signal<([EnginePeer.Id: EnginePeer], [EnginePeer.Id], [EnginePeer.Id], [EnginePeer.Id]), NoError> in + var everyone = everyone + if let initialPeerIds = initialSelectedPeers[.everyone] { + everyone = initialPeerIds + } + var everyonePeerSignals: [Signal] = [] + if everyone.count < 3 { + for peerId in everyone { + everyonePeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + } + } + + var contacts = contacts + if let initialPeerIds = initialSelectedPeers[.contacts] { + contacts = initialPeerIds + } + var contactsPeerSignals: [Signal] = [] + if contacts.count < 3 { + for peerId in contacts { + contactsPeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + } + } + + var selected = selected + if let initialPeerIds = initialSelectedPeers[.nobody] { + selected = initialPeerIds + } + var selectedPeerSignals: [Signal] = [] + if selected.count < 3 { + for peerId in selected { + selectedPeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + } + } + return combineLatest( + combineLatest(everyonePeerSignals), + combineLatest(contactsPeerSignals), + combineLatest(selectedPeerSignals) + ) |> map { everyonePeers, contactsPeers, selectedPeers -> ([EnginePeer.Id: EnginePeer], [EnginePeer.Id], [EnginePeer.Id], [EnginePeer.Id]) in + var peersMap: [EnginePeer.Id: EnginePeer] = [:] + for peer in everyonePeers { + if let peer { + peersMap[peer.id] = peer + } + } + for peer in contactsPeers { + if let peer { + peersMap[peer.id] = peer + } + } + for peer in selectedPeers { + if let peer { + peersMap[peer.id] = peer + } + } + return (peersMap, everyone, contacts, selected) + } + } + + let adminedChannelsWithParticipants = adminedChannels + |> mapToSignal { peers -> Signal<([EnginePeer], [EnginePeer.Id: Optional]), NoError> in + return context.engine.data.subscribe( + EngineDataMap(peers.map(\.id).map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init)) + ) + |> map { participantCountMap -> ([EnginePeer], [EnginePeer.Id: Optional]) in + return (peers, participantCountMap) + } + } + + self.stateDisposable = combineLatest( + queue: Queue.mainQueue(), + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), + adminedChannelsWithParticipants, + savedPeers, + closeFriends, + grayListPeers + ).start(next: { [weak self] accountPeer, adminedChannelsWithParticipants, savedPeers, closeFriends, grayListPeers in + guard let self else { + return + } + + let (adminedChannels, participantCounts) = adminedChannelsWithParticipants + var participants: [EnginePeer.Id: Int] = [:] + for (key, value) in participantCounts { + if let value { + participants[key] = value + } + } + + var sendAsPeers: [EnginePeer] = [] + if let accountPeer { + sendAsPeers.append(accountPeer) + } + for channel in adminedChannels { + if case let .channel(channel) = channel, channel.hasPermission(.postStories) { + if !sendAsPeers.contains(where: { $0.id == channel.id }) { + sendAsPeers.append(contentsOf: adminedChannels) + } + } + } + + let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers + var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:] + savedSelectedPeers[.everyone] = everyonePeers + savedSelectedPeers[.contacts] = contactsPeers + savedSelectedPeers[.nobody] = selectedPeers + + let isEdit: Bool + let maxPaidMessageStars: Int64 = 10000 + let sendAsPeerId: EnginePeer.Id? + let privacy: EngineStoryPrivacy + let allowComments: Bool + let isForwardingDisabled: Bool + let pin: Bool + let paidMessageStars: Int64 + + if let current = self.stateValue { + isEdit = current.isEdit + sendAsPeerId = current.sendAsPeerId + privacy = current.privacy + allowComments = current.allowComments + isForwardingDisabled = current.isForwardingDisabled + pin = current.pin + paidMessageStars = current.paidMessageStars + } else { + switch mode { + case let .create(sendAsPeerIdValue, privacyValue, allowCommentsValue, isForwardingDisabledValue, pinValue, paidMessageStarsValue): + isEdit = false + sendAsPeerId = sendAsPeerIdValue + privacy = privacyValue + allowComments = allowCommentsValue + isForwardingDisabled = isForwardingDisabledValue + pin = pinValue + paidMessageStars = paidMessageStarsValue + case let .edit(call): + let _ = call + isEdit = true + sendAsPeerId = nil + privacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []) + allowComments = true + isForwardingDisabled = false + pin = true + paidMessageStars = 0 + } + } + + let state = State( + isEdit: isEdit, + maxPaidMessageStars: maxPaidMessageStars, + sendAsPeerId: sendAsPeerId, + privacy: privacy, + allowComments: allowComments, + isForwardingDisabled: isForwardingDisabled, + pin: pin, + paidMessageStars: paidMessageStars, + sendAsPeers: sendAsPeers, + peersMap: peersMap, + savedSelectedPeers: savedSelectedPeers, + participants: participants, + closeFriendsPeers: closeFriends, + grayListPeers: grayListPeers + ) + + self.stateValue = state + self.statePromise.set(.single(state)) + + self.readyPromise.set(true) + }) + } + + deinit { + self.stateDisposable?.dispose() + } + } +} + diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 64f5aa38be..bf2f16740c 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -14,7 +14,6 @@ import MultilineTextComponent import PresentationDataUtils import ButtonComponent import TokenListTextField -import AvatarNode import LocalizedPeerData import PeerListItemComponent import LottieComponent @@ -2770,15 +2769,6 @@ final class ShareWithPeersScreenComponent: Component { let navigationLeftButtonSize = self.navigationLeftButton.update( transition: transition, -// component: AnyComponent(Button( -// content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), -// action: { [weak self] in -// guard let self else { -// return -// } -// self.saveAndDismiss() -// } -// ).minSize(CGSize(width: navigationHeight, height: navigationHeight))), component: AnyComponent(GlassBarButtonComponent( size: CGSize(width: 40.0, height: 40.0), backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, @@ -2811,7 +2801,12 @@ final class ShareWithPeersScreenComponent: Component { var subtitle: String? switch component.stateContext.subject { case .peers: - title = environment.strings.Story_Privacy_PostStoryAs + //TODO:localize + if component.stateContext.liveStream { + title = "Start Live As" + } else { + title = environment.strings.Story_Privacy_PostStoryAs + } case let .stories(editing, count): if editing { title = environment.strings.Story_Privacy_EditStory @@ -2913,7 +2908,7 @@ final class ShareWithPeersScreenComponent: Component { inset = 1000.0 } else if case let .stories(editing, _) = component.stateContext.subject { if editing { - inset = 351.0 + inset = 383.0 inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight } else { if !hasCategories { diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift index 731bbf596e..b5450629a1 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift @@ -55,6 +55,7 @@ public extension ShareWithPeersScreen { var stateValue: State? public let subject: Subject + public let liveStream: Bool public let editing: Bool public private(set) var initialPeerIds: Set = Set() let blockedPeersContext: BlockedPeersContext? @@ -74,6 +75,7 @@ public extension ShareWithPeersScreen { public init( context: AccountContext, subject: Subject = .chats(blocked: false), + liveStream: Bool = false, editing: Bool = false, initialSelectedPeers: [EngineStoryPrivacy.Base: [EnginePeer.Id]] = [:], initialPeerIds: Set = Set(), @@ -82,6 +84,7 @@ public extension ShareWithPeersScreen { blockedPeersContext: BlockedPeersContext? = nil ) { self.subject = subject + self.liveStream = liveStream self.editing = editing self.initialPeerIds = initialPeerIds self.blockedPeersContext = blockedPeersContext @@ -721,7 +724,7 @@ final class PeersListStoredState: Codable { } } -private func peersListStoredState(engine: TelegramEngine, base: Stories.Item.Privacy.Base) -> Signal<[EnginePeer.Id], NoError> { +func peersListStoredState(engine: TelegramEngine, base: Stories.Item.Privacy.Base) -> Signal<[EnginePeer.Id], NoError> { let key = EngineDataBuffer(length: 4) key.setInt32(0, value: base.rawValue) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift index f13ae01f7d..124221db3e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift @@ -17,6 +17,7 @@ final class StoryAuthorInfoComponent: Component { let context: AccountContext let strings: PresentationStrings + let isEmbeddedInCamera: Bool let peer: EnginePeer? let forwardInfo: EngineStoryItem.ForwardInfo? let author: EnginePeer? @@ -26,9 +27,10 @@ final class StoryAuthorInfoComponent: Component { let isLiveStream: Bool let customSubtitle: String? - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool, isLiveStream: Bool, customSubtitle: String?) { + init(context: AccountContext, strings: PresentationStrings, isEmbeddedInCamera: Bool, peer: EnginePeer?, forwardInfo: EngineStoryItem.ForwardInfo?, author: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool, isLiveStream: Bool, customSubtitle: String?) { self.context = context self.strings = strings + self.isEmbeddedInCamera = isEmbeddedInCamera self.peer = peer self.forwardInfo = forwardInfo self.author = author @@ -45,6 +47,9 @@ final class StoryAuthorInfoComponent: Component { } if lhs.strings !== rhs.strings { return false + } + if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera { + return false } if lhs.peer != rhs.peer { return false @@ -105,7 +110,7 @@ final class StoryAuthorInfoComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) let title: String - if component.peer?.id == component.context.account.peerId { + if component.peer?.id == component.context.account.peerId, !component.isEmbeddedInCamera { title = component.strings.Story_HeaderYourStory } else { if let _ = component.counters { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index c5a601a61a..7e0ba76c64 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -920,7 +920,8 @@ final class StoryItemContentComponent: Component { mediaStreamTransition.setFrame(view: liveChatView, frame: liveChatFrame) } - if !component.isEmbeddedInCamera { + if case .rtc = liveStream.kind, component.isEmbeddedInCamera { + } else { let _ = mediaStream.update( transition: mediaStreamTransition, component: AnyComponent(MediaStreamVideoComponent( @@ -1113,7 +1114,8 @@ final class StoryItemContentComponent: Component { self.unsupportedButton = nil unsupportedButton.view?.removeFromSuperview() } - if !component.isEmbeddedInCamera { + if component.isEmbeddedInCamera, case let .liveStream(liveStream) = messageMedia, case .rtc = liveStream.kind { + } else { self.backgroundColor = .black } default: @@ -1205,7 +1207,8 @@ final class StoryItemContentComponent: Component { #endif } - if !component.isEmbeddedInCamera && (!self.contentLoaded || component.isVideoBuffering) { + if component.isEmbeddedInCamera, case let .liveStream(liveStream) = messageMedia, case .rtc = liveStream.kind { + } else if !self.contentLoaded || component.isVideoBuffering { let loadingEffectView: StoryItemLoadingEffectView if let current = self.loadingEffectView { loadingEffectView = current diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index aed4b736a1..e876c3d7ed 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -4218,6 +4218,7 @@ public final class StoryItemSetContainerComponent: Component { let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent( context: component.context, strings: component.strings, + isEmbeddedInCamera: component.isEmbeddedInCamera, peer: component.slice.effectivePeer, forwardInfo: component.slice.item.storyItem.forwardInfo, author: component.slice.item.storyItem.author, diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/Contents.json new file mode 100644 index 0000000000..a2b039bcd5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "infoheader.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/infoheader.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Info.imageset/infoheader.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f1086cc8488ae516e937c213f1214c9bf37050f4 GIT binary patch literal 6762 zcmb7J2{@GP*S9Z|HL^yNrBX37m|(gC$Z1{$)hwHzQhDydBvdEDH$rDOw~l5sP*L!KC0YSr`He zh@nz2n6w;H1}q7cl19SdP+2fcN)`%}LCEbs*uX#(FSllI`%AI9866z969jxmNC|ms zeZ(K@LsT?Tns^);3<09V7+}d@TO8hoh$RtRi5M){8f%BcgJ9BN8ytqb^=(6jaY8$T zD3c95No1^(CIAA6vWz}30dUT93o9TA8*o9+69Is5c)LI42CmM|j#wuwo(zV96coTV zSX;oXHriPS?SutGlq8iURd$7jp3Yb>;0&My+w=xQu)&g?(Li?4csnfU6cnleKBcC% zk;*^Dwq{t{V(ihxElrfN3KWHslikqY8UaAvh;GLVj4P1{IJg19hHn%HaCqzvO3nml z3hLW`eU!M>)N3`s&KtK;S52U`jk>EDB4>#Ma2xYepK5 zklT{zfGO|+t8D#Sc%&4^;dHtBc^V1sb77Ly)E8@OZfeI-72cG*>+k27u{W9u&CId) zxjCoaHBMC&otYwo>V9Xki46y*-Vg&ly`h;u3+Nwt`CEcV^Jx!|ziIb8GR}YZET{6S z@3)k}FL##H*Qp}$v?wU?K2v>$mF0OalnuAgJRNg$0Mez5A2WGJid)p z+`{ZswHL&$+O3#L!ZeJMk85&*TVq^6X))KF5w%MNnmFt0$FMG)K5IDski{Xo>y?~` z?9MJ|2g)UlORFF`PtJCIcmgh$^;%OM05B7i^72dC>F!480IpyX$t=HXIzueqr zb7v-k2AsA>aa!^*>NfI#N(-J(5v3E#(Cx?5LEGR*?dV7A%rNm>a3MgE10?do;W<4s zm_A66r;n<{|CBD(oZsr*{rjomciCk9WbV>G4G0+ql~LmZsG%(PtXQh(42Fdgu7Q~t z^}t8(G2m1fBKd7-VF!(y1Wjq^RiboRxY?NxGC!prVA!WvuZuLK?@>CV^5t$w2Rxg- zf;~);tHZM6FduykS|FEMur?0LHRne=tn0CFKYLc)_^_FWoEd|0UDmMef`|{Tu;2a3 za~=Zpx?;CMXBq1iVS2|6!AC*+0{5mP(tqjQ&vr1R&0*~cs0fJ8IHY%=(?oWkwz^D5 zoBf#?kSuR%_`B;;sQAqDcauBcem(h>bB25A*iHY==LzT(sG}C$A>o)C&2_h}9bnoC zEeX`Ee4mAQLN2@zKnp#&(x>4l;&!@(!<%U^=*P=Bw0gm7`TaVqb~i~c=s(EI!=%{V zIov_+)NdjL>wKTbOb&PmR7CA4s5g|xz=bj?c9S%hw{$MxdtBFe1k zVa_2?wSkh_d1@_!)nkhyF)dT0h?WaY@Rr$@ z>gdBwf=%8{`EizqEstI`T8mq`N2ByY_uBpEdVTkF(}7J=18o|WW1q*DoseOY?V&%F z^r398PP1Fh<# zej}t7@>03-VpX9b{eA1~S|d#(34LUyboMXYwa+vW^egn0JhI2XT25NHrsq^1wS-$1 z>$xh<#t4|dx+JOTly4d`AnN+G;^7i;_V{ewxA>mJyj?seAfr;PmL9evw5%p|hN32z z>IPU?vcT&k)1;atToNN3g41z$XkUI=+#%N9)n41)pxC^aen5PH_o-z^?3nP3jTe7) zvcmn$^p9Dhoe#UldJ;R~UFPY@nScfIMg%z38t%wqi-^$R~- zq?PmYyN$!u=~3vi5w<#>Iv3Fkq9vUtGQBbb8@MrYAITpbK2{zw;d-DM-Fz@+@WGRZ zsW^Y!jWJ1?zSehIR&GtX?fWqk_ENI>hy&J<{KBDHYNs(TmFum8Rl`+7>W`iCZ8_3X z9d|m;GVgnCbe==*&Aiy$)jU$}VD5S$taxO=uy4p5b#b)FembL9HQ(u#<6=pm)6agr zA8`Xeo{SeRTmQ6z6cV1KId`}Rxnz_1th*AFT9rRvP!G+_`e@te))=8Pr(>NZqjU9y zOiE13`4bTkglVg#S&>J#>YE!M^*@QY-mh#Z6LyWQ7%Oub%6WUIV!V8M=V8SNT-`s`jdmtupqA_dYY%IU79xa6a)@@wEBR!Lh)p zJ(GjuaxZ&rNd2zgC9X2AhpoM(9`%d#`{kE)PAfhr?y2OB_$A2O(wPzI0BkK*@)|f& zs0=cx7pbS~)sf#)&=L@5eEcepqfphsvcuN}3J;CiG*#-hcVu*o>}xnCcC4ayv@WCW zTkX3*?;E^<=R}W-BGiV03?sNvC2AuXwHXeX4~)I?hn^9S4@jMWcsa**=6t{K#^_PL zj)m`)uoZ!}q#{^C!el}WEDd=r#i5OS{A7u=+pU%l!oxMA<MFFUGEG_XG2FU8+XewEv_61gwV6>$l6DZ!VN zH#T5?IW2$s;`~Ri8Lx?@$E+s=OYnUCUkeHxxSmS58N9KrepQ~_Sh9de7Q~jpapMG} z5wadhD7`_r+|twfHu_}6TX%2YJ7#x|5bOh|!$nVao}@qN?Jhas>ip)7kHy+7%_ILV z|MnY0wc}?@)^+FVzE70BtyY~=4V<@{XPRf3a2@o{Fe&gbpZZ#Je>v&gSMf!0!hC-J z)x7SKnI`HF)Xmhm{^-KqfF;KH{VQg7bQN?*E_Rwc)GL4VYxUr}^tHN)K*4Z(VF~$P zW7)%BtB=0V`Rq{ltqaqKNo>5+bG>I~rD>($u~Bu&-MJQ*5tp&xVXn8XY_6_{;W|7$ zb+ge+!3LdK`1R3QgUhs;={b7$3x;LoC6TKiz9ghEw$r7IooG2VI2rN+606<)V8u4u z(`nl9oN~xhcwzC+l=bq*h%Y4%EG4g;e)ssbg^R_aj~-)zXenl{wqnogR9tEd??*_u zTi%#J4Y$h}e*V|QyB6yBe2b}a;={(wFSQrqAN_cf@;J4!t?C`IZcS}kyE0k(S^sKp z_vQD4J}o0g<4%o(PQD?_CzjqWLEcSOkUmK*PlR2=()tD*WBAFc%C7lyY9j3Y`I_}5 zrOyG0Ba-Ye_UQu>r;hsOPL(YMH^-)`o7_3})})%JzeX_E<=e~P-k z4786oI5i9&_Bn)qX0mAg9XrsH*j#mndp+9+yYemm%-i)UvB)hs`{!eA%{NjObg_MP+mA+|-mDkgWx$R}`SWB&%r@pKUOU?+a+uTWZ>lbsj zoj>w~e*EA^uw1U-Jrgdf$@3c979N|Fa+I@ErAyvSAyp|pF?K!qeDG-|#TGXz!vj7c z2SG_!TfGaBO2cXNSB>5I%55wzHz^PhsRJz{MbW3{GjNDb&W<- zluq;uXE;hf=GF1fWoRZB;QOupzwi*~6l`+lp_>BJk#GL+q zNYioMIx6znHTp27PEktJ`Nu&CMmYs<_MaR5<91sL+daHr0z))aRFu&qtPPmbt?C2k zmL0XU*` zKQqYsr)GJqk-WArI29ZbL1;Xq4suydox0*tba;6}^8NYXtczk1(;r*VkhS@d!Nxb4 zH^quWU20+nzqU-xM|ma46eHV9`K=R0jV8O555Nz!CxJZ(&$hF{I=9X*1LuVc*RlO!@>pP!BG_qZRtIR*gM^C^m{7AhppA9O@G#jF?UBF zS^M}^>0WQ=?J0fr#ISkmsQyE!5@yeyDDH;+1_RPVCTtS*dLjfT;~Oil`Iv3ga}OE^ z$GTxkxh_BH@s`ZOzHe64_vA+U6O;G2HPo}vXv@1cc-(R-i4z|3pVe9l8l9Ay^bN*v z^i@7xLO*lnyX55R4o@dhtn4NNjSrL%&fqVUI`A@6Ihp4b$Kx&X&LKpl>L? zu`PE?e{E}5{fS+0^r&t>Nb~8mcth4J=9zSI&I?DMY6*p}?@n1Vy|aa05|U!-@NVun zFc9V}+|nfWxvz2j#P~HMiNsu)CNZTuLiOXvJD&4^m;1fagJID9rVzf|FHuuxACUQk zxnE%vf8+!i!*y7T+EB*Xr@mMW3xpEAFYOzvz%p}Hg1wtV*-aqGZ^_~vg}JraLDiqA z)lWp`O$+z#OHL9!lDa<=HacUcr}3HTLAY0NIoqjdPq70`W{IyYWnbcH!t6D4IhT*N z-%^lpb7S~UJlx>OsheFXiRJW zC2I9s4t27zx|wA-IuCTKpeTriK7`*hU%phGZDiU6|amHQ;)83Qk%>OA6t0}ea zyU7Y+StO?TnvIOP=>oM}c5<-*Ka;)Ei0bima zWhx!SzkQWSR1Nz1zUbScp$Cw9<~dGMw?hL9o4p|Amc5Pyuc=$iW_d9)^kI*T zmp=c9im4{G-xGO`{B!RP+`MxdV%1%ABg{QOpbA#7WD(UBDX9DGF@X+kSeDM2L@Y~&B^q++3H#FB zDuTT3j5>-Fq?5}ODR%M-knV%=BID)yK1aK`lw}gH5WUz^WuK=CtztfSP6{N{$j`LW z4-^YrAG4|qf8of_L>=$|ELH8}$dMhhho3`P<95`i(bT+98WcM&U>X&RL4*2`N&>Dbz6{e{#Q5TF;ca*Pe1kJQ{F>ri;%0XOlhQkrP~T%3etC>! z&dc_)wTpMcg?rD-{(Fof?4H&uG}^1KMFvCQ0=~W$vbSjFb=RwoCU68Li3Z;ARw+z9VBq!K(~um`b&_|oEj*KJ z@ZOm{KLuDsNRMWz-=!qT*|lXT@RxkA;xei$vaB=;AirOC^*McIHEo@Jvaa$MYiruz zV8GSJ;QF7SUO~HI#MO18&r;&Y;VYk3^FPi{SDx`$SzDR*S^u;!mb$V=4_7$D^S|w; zyQDA-AtS%LP5#acE8+13GC%|V&b8^H0Wx)iCW2^VZE)!SME}X`FoL^J+m?2THGn_f z<=a$%eHma=N#Kpmb(@9LMN=qNFl>XL-rPWU_Vrx?l|pZx!`V<+OV|eUw@q6@R0yt= z>03T;5{8r@NPpPcrClkujBr?YEWoT{cj!<`tFi49h13Sv=3Rjnz%hf5VDa0(0BkI< z>ob7Y7Wt}z2FN#p-S2CA0Ly@&V3<5;lSHN5!3a291`f7m-H}n^+8Dv_$`F88f5>1m zK(q9ROj-tr?oSyU=zac>A>e<{MaThx{-Gxa1=^qAWiT0;zx05gD1cV%cRg9C>|b+b zq4Iyvg(3cm7cMXVmpwR=QZ2u)fq)|aiUR>d0M)XsM<$|ij#z-}2U4wx0*dAcTpDHu zm6L`+Wn|!T5`TT)C|!s?fdG`!4o3_zzb%7 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Auction/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Auction/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Auction/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/Contents.json new file mode 100644 index 0000000000..98dc4e1443 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "drop.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/drop.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Auction/Drop.imageset/drop.pdf new file mode 100644 index 0000000000000000000000000000000000000000..85b923462f7d766688f40b0998c7331bde7d2659 GIT binary patch literal 5869 zcmai&XEdB!+sD-qBuey>3?fP}27^fuy_ZoU2w{c^ql`8>(Lpb?HJ7#eZj7vX|5Luf0?{9Qr;d9elnD%hMa%OT9{ z%n^UDs3A}mXiI<~SVZ)1MFSx8R~IL=BLZei;+9l_K|-&M(6-vS?uPT>;u=hgane@_})NIptC!+C+imlJTzB5qDMHPQ5tGpAia0CA{d zKu%8)dN;WSY}vWem)3@k$G2WNx{B8Q#I#FGWl(ZY*PojuPLYbE zWn%1*sbawqhn(J}^=_@BzD|zQc>ku4#jz0|NJ%e|SD6yf@zQ}L<)xp!NaJCNG7=tm z8{yFFb&6yNJs2kltfjnVp|Y>Uq9qc`VgNUgIihES>pTv#6(&Ep3F-~*-I}N%<)0253o#zc!aW1P%>)r%dD5naYrxFNHs$t zx`cz$_hc7RLwmqE6WG*c6MCl>E6Qf^Zp-hS%e`ymdNBw(XP zaO?WZpth!0a4V2{TzedD2mL$?ZRq`vj4+nBPllCj*jywkDLjeCLRLQQ!W2vT#h5j& zSUh$5NcdGu48%|FO5sZ4iaQj^)a3o)<@%@_QysY^3GmaxwCV=u2Fw1FW2a*>X+Kjb zdo}_px4?=(Hkm7F;r5{d3URDFRN*1~pP+CyhubA#!%}r>UP@lFY*A?Ch|F&{agn6C zVdw;OjIhk1k{(-Pw85NZX<%KVSJJq0gXR<2G$MO42aixjciq&%NY6k$tn6ejU z?Hh9))QNdG2t}f@zFcQ9B1N5e}wvFeb5@tgr?bRz?5KyQF)ey z^f__q`Z;de8wp>ls5IK3Z4aMizsg6eEk*Zn(Z~;Gl;#+H7qPmsBj_CVG>f5kxLnQ< zcZJgsXscqNJfa6tgFLL3IjAeuCCq~7H0mkqace^|1amf#e)D*dgvW%nw8FfLChL!! z)ADM0Ou!};TFz2CFBy$HAMz>N78{0+vOAa8!ZB`oa65>aeF+2 ze?`AZmtEhiz6BJX$k?A?n9z`bOdtXSks4OHmetlARxy^&mg<%|6~+~Wqa34jw4kkvXjcq5Zdem-`a?hQ65Oy_bIf z=*$oIqc8G>sr`r4*6I4RC|K3Z)h5~|2lo5ymA!W|JTih?sLe!Y(X&>wwYT)Iy-|*C zV|_XH=56k4q(3rfiBD*_V^Yo31zXU~Y_@92FI+5g1O9^HdYGDmgxM#VX81Gt2>H|Ms;)c7)GSpwjOR@x*DP0WjayHo zc*eQi^hnxt`-c3+y=k-Q)$ItkHFx$%tMjVgtoNvose9lS>v?atcjwt&?q2+6#g_5f z*i!HY>H65R=%+8{P9x4gxP6Gu!cQk~=X_uIZu(}cs>Oz2%K3s~4}lZ!wr2zb5RC{v zKfnu?D&V@-3oWQePjP!mdjRGEuMe#aOC4(!XCPzgtvPdSEwsBQy=R7|<@U|nHH~vk z=}q4oCxbnM=z>+*dD%r2#zS-?spTpaX3`tetuo#`@F*UC@5npKe+TGcAJd!nZb7XFPu7%o5MeQ>rEm}Z%_;p@P z!t2r4#~4iPEp=CwsT4^L5{kjcQVjWmhRworijF#P-VuI=HgsnJ_V@)2##!Vc=wY0< zQ{mF6v2{xEmcibv$F|4nVbPU4OqD45k;Re{t83-lE;>Vx>pQCxTPycbkdl}xFml-r zq6cY)*u4v~vu+>kn25exGvVs#ootlMWoH?@6~TVD_b%aGPglNCXZxWcuScglc=`T) z{@p?2jm!7+&!D?aKUS+I>g9LkgZE7Li1)}=oyR=W^-J80Hx?VRjuKQCISx4N_KHV* z3I{5;vAADx+i;O1(WPGk4vF@dkByR{lF%80Uj1CH>io?U*2%Qfrqy7k2+Ql-Vw+1j z(~I>yQ+e}NP2c;>hRxzzlLrF_w~w*MB}IDmm8rY!4l@o*&!(?UIA3*k<^*fd4mRyX zA3oFR%|@Ng?dVwJXQbt6WtB_|i}68DzAnV25OotIE!}AsA6pOo2#ir5cys(X$K7^I zS5+po99&wlmULEKB(hNX#)R*Q#AH$bBZo%^URp#Y?C;EW8*51WUn5_?q?-jsxD+li zHc&e(q4qXcC);sjiyv)NJLa}#EHvJa&0iTxDtcYpRX6F_bgHnWUYn@?e&poKfc4av zSNn|KvTf^_t#|0rox_Pk;N(V)(>MO3)o?!qzIVXwOKVr;$(7eOR>P+>8_o`;=L6zr z_{c%zTQ|7HdAtiYst%vEy;R7p-#!i*!ggk}Y6dG-X8?V=_fNL;SA{;8^(~BcFSppX zjB$G1LcP~Nfd4>@w#T>C-J?Fs@j@JbkG(f>RtKR0fp-nJJ$8#5MXl^2-dA{!fmwu4 z_KiQ+#_t{67?4cb#~u%FaUb_@#TRr}bt6pF8p^$|>>t{TAY69ulEXjlKJHy9WLa5Z z5FxX!p}QBsu1wpH=!%F*NaCUFmF*KVl6);48y|C)sQFBSSgPFxNB4$TC@V>VPlsnI zM0z@f(C2|GeYN=`>(+PrR$!bCBo5S5kYK|Jq-W1{QVS9AKXCA(GzqZ*t9|IEBA$&p zA$7+q0o+%jBVO`h>OX7PdcXWIWvZ?uE`=|7SqN-fs6>$hcvBVxZ&1QUIe$`4zRMC- z&>E&2r4LXBWwUGaUG)vCk{5AOl{WvxZkF89)c_*Ed4TsJHhp9yK5t|Mh~vU6lBfWpQKTl~FfEDdhK@NsK=ryP8Hh z1-uAwpVCF+Hti7|OTK&#M`+u-qI6H9!yx_z-%WhB?B1Vj?8n?&JCi(h-){%!p(=mt!Y=t22%RV9 zn~v&TudXaI#{_t3SY4T&;7ed(1A7GLTMM{2O0lRRH3Mc9QdB})CcBCoc|uxuRJy@9 z97%21u~gU~g??{&L!o4ijV6P3_^bW*+1`!FR-dN%`5*%+C<^bL{m99X)BE1;nD|u= z>|K#Pej6>@O&6PyT40QeF0uSu^VZj_k#epu?5tmB{SERO3w|TVvf}!Dc>LDF>{{#E zy-|$hFz2H$i;ic0En$LhtMFFRw&&K}U}E`(dpY=gc&&uxcikr^A=5%$yi1*4Fa^N7tZNi&^>atyGwU0K3# zi}GQ9Ruo{$aQRuxjOEQV1-P}%LhHm7IhuM+Hf)hzEyiK!w)HAiEcS?fZzfc@EXJbU zW{ot_fg*8pw`2D+W96oJZUOFkK>}at-N&9L!|4n#@hO3^+TuthL7B@eofsNC9|h00 zf)F>-b(kw7CJ4u6l2*eKAlyv@+}*84H$`8kwrxhG229_@Rl2r z4QBg;5`#)9j3u_|XSLU`}s| zo;ALXw|bicrxw0l4S%p~E?Szh__mg?!glRsd%6KSb`rHuq}RL2^>te2WJdKeS##QkQZidvl*+w5y9Dp@*#;5WKV zqL~=4%~v>UUY~oRX)k(&m@t?BwO*nc+keT@%yJtkD}F7?C?> z$taG%*3#em^AZTL^y48VtLpWs-%2%(kQk`0MB1;lk&S2kFf>8!Bu+B=##u4s2fsLws|C zp)_@0W#_+(q;k^G^Tm%id6`PUilQddfmAv-EjiZnj1>+@ioiLc*}5{_6!|fS9=+YR zwL-y}_sU0Wb#Z&0mS8d!OD_COfsyTL+nTC=4^{-9o8{GLqd5{}?!np|qIy*NlSN=E z7O%McvOl3ODRYYq#`}G>vRo8kTJu0$A!K8^57uErju8^`-4mq9X@o+Y)7K(@3yI?pb_P zErfJ8_)@IqbK*VPyUV>J2Iv=w*p_8o2xayvrRwN-P?y`o!aD2gmnJ4|s{9xuyvfmE zByZ}>D!eI^Z$4^(A)nhJ-1S zsX1K4v_C({eHYcqfM1@L5x5&K_sydaq44d^t_zrve6v}FKK50kD~}8&zJTQO&AnC6 z)}s8AO)DXF?5L)e1RG`H4&mBUqYZrc09;^ccKIcD3aw+Wa$11)Y1Eb?=`{N%gBayy7R zK+ragCzwPyd@gyTj(ptpN6jd(crlILMj>C0G{UQRZqOdbD{2&$- z)Rjr#o)s#q9Hjm9U=&MoLyW}uf$!z}@aKKpn<~X2G4nj`sa>mEuAN!k>8b9sm zLo?17>swkB;OLu!^hlnl_A8o4MQM6?RfJ(KRLLuHQvzkEEgU6kO43XjVG6N;R=FIL zkn2?`XMv8cdiZOyep47Oi7$9dik&z;&b+3d<4Ad)R?}R}1h7}K+!-zuM`ikg5=KfP zOM+e2a(!;b;RP7t2@g^uWSSX}$`;ESb7aHX32uw5tG%X}pdH?bF-gO|TPfMeC-X%I zJn=5EnrRqBew}IK<^se=+1XnW(V8|IY%YG|WZAj3_=6Nm`Z}KrI~uII zx+ti9G>AyG2Krm#HsU2nwCl|o%M^zl5722{1)Z+@syIhXo}K~I1I@&f>Op8!ugU@4 z>=6GZUVj0>x=s9wH;kE+Q^?)(vFDB_b4akXcDNvv| zgRcc*mYIQkBT1Le9EbeJYI|~LJ*ipOB)rP3{ZXe6MRz|PhwC9+ZR#E8L$8v0(hUDq z(v)?1w(~r-P@t6_e@vO2f3*~8K?l0$vJrJ(Iwlb{BLy!R~_hh;*MYtt{F&O(If2k5ZL8q6qfg|6uS{$8#P{1$> zlg|x7_RjDnejtqg2L)YF#$S$~AP6M%n{Ob$vR?@hw%6=I5IQT%6&8*?!e@zR3R937pQA|19;V1*(Tc zxFQ^(j)=#I^X?b>yjcIWC1uoOJHUkt`b$uQp)3Gg2-Gk0^I}BjDZjRSakjr^CksQv zZ0s!l?Co~mhC~1W5+wO+gcrX65inQ?40wFyw+1?A%bzQ#e>9QvUHz8^5i~dhbSO7=uxx3qq9WEo#)L5u*2+2#MZW1q9vS$map_FB*S?ZpIdh#3*j*Zpe{ThfENI>u*U-cL`4BS zGInk*j^h8zs;^pmCmb%VLXEun4~Dt>cHNx_f+LjW%~@8$L9f&Vw@&yz7q6l$d#0uD8I z!b75|K30yQ1>?)?dqo-%@uJ_)?b{{eO_80VS-ADp(j#Zn0C6pjff^s1skFJ~^h`G? zJfi<}Vrf?unrbh<(BP=6KU;B2x6GDpzcI-%s_eKII=KH@fjevQ<35eb+*@qrVt~ZN zN$FK^!EpdB*dN>O`s&x02xuRfm~y^Gq9N`h9OCbF)sn2LK{cFCjpDAx~;;t1kfq(KdPqo1b9;@#_RDYc*}-Xg}}oO1Gj30!+i zTVzKO`XQ;m*w9^3X37Nve}_JBG$>Pa_AOo3wnTw3`t3%ueTjy-bO_F{>%E9P>KsUp zF3$`R`O(D9xTaxTfz|nKu5x7?iHT} z@S1s^DzIu0QhD3HriU=!<4qRESmKrx%sjT48R9A`72Y0ieq-XgK*JjX>U*K9vl@4Y z*NiVVP^qjW(Dpt}ytB%B*lT_v$6!8;5A*Z|+Z&eEaTni;>caH!M(m?nBx=Uz#=L#EH=07c~A@D!t>&!6whlzG7&BS>HJ*k0Fp*G1xM1$NO z!Ian=6J_FevMN(1R@(OZSLK=Z&D7z{F=1Yg*!}NAG1STKzbB-ypjxK6856g7tV%OO z)=Uby=6UV2tEw67kjDJ#1I)v&=4a>nI9~q9tk#n=TK{H$lk-!z(hM>i zLGmJRIir#mDrrJJ>@1-g@=`x-`uCjn({VmQkkC(FH2^>A>5rYrjOR&q5Yj1%UYO@z zG#470en(4~YSNYJFs>&-0gkyO@mI`oiAmawud4@MmzBXZlE9XwbwTJ^5norI#m2(Y zG77@S8xR(bq+zeXxP|znI##M%G2dF!c;D|+#>~g8*8xKbu6WrA zfi9qqqbfx^ix3W|>xl1}9d*>VF`Bi81 ztNrw207vnK^9&s7zAH{;WlravS?3P@udTAaJwgX$Uz6D7dM5K2d`o)JnMy)N?IHHN z0E#ylJppKr0T?dWTWvH4!4kxH4DI%9ShxVJ2ND#MSL%X9Ay@VSPM^@yq6t1B6bRsd zf>j$FwTM@b<{XU1i=S_X--M~TNS6`_z`3dpV9&?4lg5svw!#3?>-NzYpkqnLLGa0l zaOrVt(WbG*_EkXLm^bHFYR)YDBGF+ODBwo_J0E|Tl zlAM<4Y3IhG5mLwqTetJ+qV)lTFUHM)g0ITR8(5IrkMWeQc1r-&Ikf=nc;w+EIiQ>! z^@1XMEsPR^vEY}%@wv?Ew?^~?$W`U}-@LZH`wdTkGBajAgj*&#@7|O2k-4=yYuCS# z9dU#OjkKkhXYe|zU^3rK2<`8DY+(;nO&Lr<8>0F^M-ipne#4xu(tlFHk-q*r5A^nCCPal^eRMz81S;=L`@~VrBK7s5B zzVo?13@e}>nXHvDK-*+7;BivYSDexTtAGs~qz;>^wXg~-iaK-@b=Wk(d3;4Xc7Y$z zA7PzfHBtz0u9vwN7u(vM+8TWbJFu>ijEZ?74^Q0 zddo4gF`7O)F>YGYD%oms9*EW+U>9rV()Ofxu{kHsynczWlcLj=QJb-DL?q8UFT97$ zQfL{mY`@&dtVdd?7~fByFjH7roM{(i7rM^HKRGn7V&>6THcV@|Wy>v40lH-oOMUa9 zikz5bmsGb!glvp#R5ypR-yrK?bCOt+Y5C`}_;UNQu=2#R({i`6nX>b0;Pa(vt;tzq z8U2+S+uhu8*$Ssuj)!&OPG6?fHtf-ExwrXRKqH>UEX*;a4kYmS&XJg4oQ8` zmVcP{blJMsqxX^8o|?r|ezp4|{22)u_e37?fDDFAjcSnZWG6zGHC7qi3mSXsZ@MSG zT(5VXEt$)DxzVsY>oAw?lY+eMow0*lwOeJ|aoq78hFdsUxqIg{`8Mw~dp9RG=^~ST z?(U6zkJvBXPu+RGYy4$qJ$(Dx*O?8Wu5l~3DfiE8_phEupUt7I1jGjH1Uyw%Nq&%2 z%N3e@#54Ee+Y($CFDjm^NLkp zBV_nZ?wcj@9*)}_FFRH`b2~qE%!m7gQidxtax#MCW*=xhB9p0;TgvUowa+Wm^{$w0 zg>z1Gi|}~6B#xAP)}GKQtxz-Z^N&8c@j9&rn3D1}B>|WX4$QEBjo`df$LA3_`2OZ% z%Sywp5|Y%H(jR#4m7#3pI*V*lK503KzM&-;y2zmD z%-5)PoM=1$uH_yZ!_t>}e&E$2s)AA(btxQKv6ySLax+5O74>yIM(6Xc4Uznwgo=zH~){$99zLmtol z;e*p%y)FKMn$eHb!y7$LJu@u6%+9TPhZdiq(}St~O?S!8i+rIcpOWv+oi~BWfr5Jm z-@NxKI)v++{m?yPH}zTt5soRcWd9BRDv6 z0YN?X?hskD?^%y*meXx+QiJdvUQ*tTVN|4e3w<4vn3lnQeMEXx*hoB6GC4KzJpEpT z7>>lC#}%zxzESjeY4?YGs=<N&8z#Y7jyt{PgYxF0QQip;nw8KoR(qQEikk;9pjLp3 zT2%vJ{JZ`J^8RMKC|+fe2zgD!Me%w$176-VRn6TT{(_`0ra!9G%ToK_2OfFA1*rdt z$p4-1@(m9Pic31W*qZ;}L~CgPF0jM@LG~|j{*U^3ejE<`_WR$Ii;MpO`O+{)7+llE z-15g)k_G}UzO}{=29&m6e*pORBb5KO`UhSdqFgxim#QCaGfmN%>jib_M^Ex;iBDCq z9ytm#D_ly2c?mvZa0)X;Q!%7RH!0Xao&N&p@ga5`X=s zk}-Ws=Qq$fw~hYe<&On6YiISAPouvOY3X((@u+~F^cZasu4rsGG57|4A&Rll_m&Um z$K(a4DjGOFzRc?k|Yv zvC5xM@WW6RyhEw)t|U?_AFtOo9BO;(%3ZUkoMUuX?%IkvK}5sWE%(Dq2iE=U|gKeEB5m7U=6e|7SHytvhygD}hxM0m>*4>ub8b$RNQIkTn`a!|gXNp5(6u5K$)#;2{(m%nlRTeUAv-V0x-Umws7T+*-^41!?W5JO;Cm3Giy&)+ULA>vL8LV0$tx z30O0xUA|I7wcMbgbG$izEhb(Ckhu}(i_gq3a;u%ift2GAN4ydqUhcyLgd?8I2q%N# zA^!aeN(=DpxV zn5Z*}3I(WB#5lz)JCJpE`i!8?F-av{=L{n&3Ih}hP@Y@z#TB;Ai9kF8*th7jp*_~_ z2TNMTIRwAp8@L$?0XMnqsGI9vi!saes9ezmy>8LkCs`@dwUIQVi62?0vOgb7YcVNE5zIwW ziIuz=uatB687#Z_9=&AS(8_@-->mt$}22XL!bUeB(bh@S-gU7j_5L%<0F976PQo#KR-^Rj~ z4i-uDPp!uS9J?u?``ELN$y)YFaSu!uHB%kS+jY~>U+|_~AFPf;ta}GOYZ@@iw#p>nG zRCyh;{{GDr|I^0_-4Y?__cR2&bjd!5fxc{#aD88Rqry@&0N3C#Q#bbvNl(04>y_!D zDW}4yFk&Vjsjo3HKC>Jx`)cFJS%mu_w8UALP_DC3_7p}T%fYa~YOhim_LPpdB%Lng zvA9JPi#+sIneKQQN&jHEH3m2c8TUPw(fcu@O4f-LIZT23PArMzlR&rJ8zSDTQ&qKM z>HN=uOs``M3i{Qx1Ohy67N*Hm7>oH?OgOeS89a|992)QEct%Cfw&43JFc&=#*ENP# zY*3FdC_+o1x{J)~ft+}IN(zJY^++3bJ(=~0hDA5StGbkw7UcPKXOiO6>;si@(mOWC zjk+~DUf=CA)fR4LgyFOD0^s^ojxh6F2;m2`x>+iYz3mNM9-Zd7H2vHiXOUU3hf=A-#GQ<{`19d-4&IPq64lKC$l`)u@=7+^bv zlq{4@uODZrm-!nNL#7p_o`in3bHJUYN%RbygtY1@Z z>;;&@AP525BCs{#rV3P}T@^^}oCLI4xMvU-x+Ge2?oJSiW-&67N8Lis$$EoI2b#nRu$sNy4YWyI1_Z?#y`A6n)X*vVe|R1?Z8(q*qc1qaS|| zi36rR3!brMIRaCG7&GdQ;8mJK#qQ79>fk!37U7!$qSxgnc|Pc6ok=WBtgsRUK39Pp zV$02c+Q(rs3f$}3>fSB1-TQ!mkMpolD*HTUH9C<^bIPhbr+yAE~biJ$ycMo z!6R0EtIGSqhuJcUJ<~aVQq5m(3S(e<_aj@SUcXz=KyxT=_>D>?snHtYw|++NHjau=((Jd!M*AO({h`iS9mga|2Tj$ZQOUYE%iEaz5?SuNKX8O@a=8bF+>6r$MU%I z8FWo#y9DPLwxOh!MQhTe!!d^(r87cse2PjOjlP~VfmnFp7{-3@ zaT~d!Vn2<0PiDvxiF$|EUL+Y#Y0xex)(vyzCb6jJeSPHkevbp2zUVq3_e+}P^zwJ4 z*Af`n^~ZEw4bba|gp4v8KvJjbf?3FxuQnsxc_5ANTQwuYbbzNrPLcr2c)J|sDjOxd z0pJeRZCii3(evF(FFK1#lKeJPxWu+7pfZmN|9KZeUhajo3t}>ounqDSq`R8UB z?Cpje_H|y@B@PS}t$ul1l6Pq#C`BUc3UP!&`o}l8Oh)0J*VgBowEE#icf$uLA7OKD zv3gPsXghGF(Px_J;|@_qNT{tnljN-2XOhyWMZV26`Zn0#J(`5u6rmnj)-`v&qZal> zG$NKQ5|qfPdUX0}RIc&iw1V~kqr-T%OYE`G7GvV4r3c092DVRv@Ch=J&$$`U!&=U0 z_hqg!fyH`VYr6G23d)TvZSm2)ycciIP<+CEEh%>`mALuCQFcy7X|F~=Lxmb*!MZsu zsK#%HQ$tw5S|oCUl8Q$~bgfDJAjj2pz6)>xS~>`9G#_3g^gljX$W_WW8k2k_K(4;k&>!)E(b{m z7dk=m3G+3j&h`b)9zs@&cuJQWSxT|P7>OS9u~V8T`N_Jc_jwLZw{O*AdNTb38odND zKUyUGeEh&aVCe7dT}fwW7{U!;_^Vy>1BOA&ZGLrmRiRdP=KtSrDhlH9{AtWyV%uNs zU}=~$0_u!Xd5Ow?Q6U#?UjzX7t8x5S^!BG=b%BMI?W|Dd0)Ur*76qVxU{-0E`^EB~ zvp-I_{XlWQk>KCkTsn48PbeG$hgw4~y3VK`@&!`-Ws{<_H4N|z3SM}mf@);}SfS3p z+UOTw3AN;x%@-^`G)tQ!%pGAif7gvfslnp~06};^deRpo03;~LF9@(E_)P<%EWCW< z{D%fYS@j<@ARp?q;qNpc5Qu`m|49@0ueCsaAgX)zAAJ1$r~`?=(*$_=P@erKP2eB+ zg#XKC0Rev0|M~k`At6E38OMLpg#Js15LocPY!>F_`-@%#+}zF)3dg(H4vI3U&A=+i z2Q=ap;sf&X^Yinu{nz|*m+)x7V5lwf`>=vX)6N@u@jEV2zBwFm`GbUkfe>^k#6bk101?Jgfx;$gLH!k(x4zMAtfl%jUXVBQc|Ma z11jjFzx#RK``-KBf82lUGkfhdv(}nDGiTPU@1jwbkYWL`@&ai5VHW@dWCuE!SOb7S zAt4}}gq4ea|)D{ASI=e!E!omQEz1bNPfa4c#WoHL7S5t^HkY38l!Vb&; zYXo+&eXC0E0tGv}I77@`?aj{K{L~8PFRe&f*+OA$uu0j1p%4j(se>8hM?-fUAwV{5 zB@=6iDfBx{0b*|fwFGj(no#{tl6H1*bv##J{k+2Y^S5{B3VqW`4W^hg*xtnv>7DMQ z%8mLqdz}iOoZg9h8*2DiBj25MV_oQAZOTY zD9nl(NZGS8A368CGM+0rs;-;PF?p-G2WwZS?y!dz@!)T7?#6+JYMFy7QU1D@EPt(iK<_H&?)Fs6IT}lAZR>k+W`6NrqD!mcj)7 zai~BhCR$3>5q%MphW1r6wZ%7e2__-lUN|=r0`VD1u^(|-K}Sr9-qctwG|+_ zrz~8feKVWfTr}1=E)eIj@BR6J7%s9U1_)`qHxc_3g%p(gL}eeL!jq{TwM|pt$Xhl# z9RVCeg^=NIa2<6@;F?Dt~0b`LWA&@!{7-} z1EIBj^tzphZ6h7@$p9bvhZhr2-QuR3naZP*8cR7u;Vmsn(_BKsQUZadlMewaF zFkuosnfIFyM)jsYbi>wtGQmU&*ib6N7bV|gmT60iN4sVk-C1U8PC2o$yT%S_^ThFRq9p#_&a2Ge6RNGG$h^Tp$f0r=2BS-}QZN{;blUop^KN z^~*et(?=6G7T>ymboGG{de9%S3;7*=CG~A=HZuC!&}?)IerOrP_G~HmK8}AZ3TJci z+Y-Xd4!T$>xG$(@3(NOu%k$H;m2$N=g<3h-pHEt@?~_f!!47O?|MSEG{g`2AlL&UP zseaDbrRxcWeQQ17W=N#e_q4B8_vPa!~30A5y%NEUFzwPUY%&Aj5k5h<>0;_$pR0r z)7BhvI}xrbgeCi~zilpV931#6Hg1anj!FQIqy(bA2(l!pBUIZA8%Jdt83{>U|1LV< zB_H2?62@|b8_?C;BgIg-92#E!JVk8A$&Te3*$2;RQ(0XJ^`*y)2Z_3CVY<*bk?eL!nSGc z#Rb$Xkz@iA;y$rjGB_druYv?VU5)tUSa59`)P5Ex`Ya$B%`|3N9AIC(6Cf)-z>%lz zxKh^2B!~&(G~oT!>U#LmG-6N#4y}dUp$wS{PZF7qiHqVW_2JV;94kd4o}XGN`9fW zIzkb~NMLo!wRek(N9@xTpPri8bhUa&IzxAJKar81G6f@UWEkrUWfMv#rpkzMkwyh?8EiP-B{R`m}7blCOAmF+ zB3K5j8(U;qN>rGft6k`!x}Ey27FV%Hxkv9_!GmI`!b;pA{WZyvysAR|RUT`MPaLih zA^Fz_#-B>)!f(*&vf0V$$WCbTEAZ>pi5)dosUzi^6t-!~YBH$u=W!J7SOvVl5{-0% z)Ih++{K06;&^4#1fzgQDs9MEUnQ6GfDW==uN^3`JS2Q^`gDWE`1KfiKIca%Qg`Z`j3%iEHhnNP| z2Ga(|MvaP|i#|6z4S;X;w~8@#Y{~ALYs!fQ*Un?M6SO-~X;9S+2;_O@g?8eb@-9FZ ztQQ(+wecRw#`RFdPd%!5oMCm>DtLv3bG&a>!Pu?4q@UDu(~^~|oacr~%ysez1*w~+ zFT^@b?ny>ThIcT@`}ETHHYMInG%8&$i7T}(2`Nn|`BLgqGF5U~1*)E(R3D!6W0 zzL~$)$wbXm-?rGE+rHX18|oEI7%ET2OvNKL9i|?QFHs{kpWBveo%cw~vwZrwGxH>? z0Gp>{!a&iw#+YVtxuT(uZ{!K_>(nQptl z9(dk_zM#`P>GJwc(;kiTC(VcV5T6AN2Fb$9E3Nqy$ZTR)mmEyaDcl(2<+tzMrpDORSuW#1QG)eADh8`FnpdO%ax=wlJYFBy~Y=3CZ z|C}oSf#!(D;h=oNuXMO(uN(dyd=I?UL|oNq;4#Vp>4|=(vas^J&Vcq~mAc}cFBG#m zU)wiBNun&t8TfWq3g+9G_rTneNtJb*;JFg-|IB*v~q6_ zZ^jkV)s>E0kzl0#Q{3@AwSfZr)5T9}HVAn+MJoA~b6k8Z{9oQJCugDbBc`ti^a@RF zg}1UL+#Y^(VqSR1ZdYAiEc_{VRrSa8)4DRA<(fxEEWS5q%Z3b{43E53P%5dOneMmM zqYY(PWyBLMutm9*t`IllJFVCs>}<~V!Y7p*Zr3?K?#f$k(?}}b7)vk9Xn5T?>)ifT zYWH?S+U@5PUq***-cEV<&TFpObxqm%gnt$|o;hZl-L7|8V*R`s82~}>31mY4h#`q3 z`*C|S@~u+y>9Oehz?69wED+Z24F(}bpOWp`<9j{vQjeSVPQu2zUlmX&g-X}uvHA5M ze%aODIcX7XE3WH{@hB>DJ6?BmXrJ_rC6) z=YDw`ueC$e^J5w^Pt<*6W)rVk|h{|ypBppO=rXz5Fg~z7tRn(N=Z0PQ@VE( zRixJqPW^^=I0Ycpug|NBUvw@D$xq9jsLssLrt6uuH8)(J6`Zmc2jaF1NYj!2O9Maq z-6JP&9HTH>ZiSY9T-1fwFKBnJR01_*2vJx3NQO?Ecb~86WxZ{zOwMA-T;t@nE0w{{ z0zP^YY|<>#9qal+HdBx+wxlaUJyshi3o4*e9K`gGsFmb#kry?4L1miR_qrK`2&Z+W zB`J4eBBf|zf~}ibxg8EF_Z2A;b%5#*ds4oO?dSRS@yC9f|AMRsWRn#a7X!OM%z$Tm zRaKzD#oiUfe!+IOdHv5j)NfAnAH8hQ+3CRl4>y}l(gP~320hzrpD8a1I{#6-4R*2l z$wz=ryXe2h0FkEK>qWvy@W<+IDh$bx>^KI~{9@T@r8 zdu?5%n<-LWu-2unkI6rF67gQOByneVVRt^`h9C2Lzay`kCr8_#^90bE%@d`QNiZ~2hfdXlRhL5c z;=3kA{Jhz{53c4Q+!+Z@zmIW;P+Pxu^s|2qLEP=aW9kos$J-tOGu$>HqDRyg`G@M6 zIC-jepqqj6J)u_~7<>|@y+vU(#pxL;Ig9tQ3r>7liwY5u;?^ryx10luEmpmK{#yvG zsN7F^Du=~~CRa@yf|t=Y_9Ni+Zr3OVJSxOs){O8S5*iSR*kbE?A$-eEj&%N@((CT= z;LXYwuMe24sRuU=EcbeYs$~dBox}@H@afR{``vaAm#wR{1f}$@}WCj+6b|+Wvu=#c<{$DJy);qewJ)4?=4TO+1> zt<{DTLT^a(Vo{xQ2uKG*hGoQ6p9z3JPs*F*;#7o}U0s(^c$kK?k=#or8p*3w61ZVQ z1B!DrdnSM;spBj{iPYVh1TeoJzg}0dgSTt^Syxtq*C2{Oa+y9S`CT&UP>w|@Ei_o9 zrxUNHEJ1=wxTDcIM>STxg^nn)a$77bGDDb zXSlg6d3|C`@m}!Td#nb{h_0Fwosv@Lxx9`ikQ}mlE$FH*lN3t z6HGZ;n(H5}xPym#9)h^OSn5)xv8adieNg6tpA|A))0N$eip#3kd)&cvKtHW2XJguE zJ{gWKaQE}7ymA9*meYRj zNz`oXuajKAhoNIW+n21-qt~T~(s3cEBB8R^pp{gy<kg|#Y}U3W(*_95*dVLGM9&F%NW_w<{=un%n3 zT_6|OLQ(}c>o0LPK}?*IDuRPt!P-x$*h&ZOxvJGip@@my@??CwgsOgJR}&jVq==Nq z?TLr60|w<*O{)pq4HElXh5ScEk99(XO+6g9iV&hvz&0b}xYF=7>K`r5-w5+LktR>3 z3d;oGC}h))qs*{7@fZEm-;yA+vilV(By9MK)`KkU8i%Ra1hH-_GvWV+f;Be_s0`HXV>=|r!Z z#`2arXm%q>t3mTlwQd2cY&a?@Wqy11ka5IJJqIk!>< zl$}wF>F>O^?L|-%6lb}PY$otDY?Pr(5R^S*YnyRJ8u2<1C&FAu?4ERYa0TYP3D%u( zO!+)9Vq4^+hJv`~lee05tBa_x)!f_ibPwycb$THmXIF{7Lr*Mf=?0vig7UXS?RskR{-vaVFT*}AM;s2zpG z$NdgdDQE!m<44X>-q6Y!3`ExZkk_oL@7Ig!)N^aLSD8MCcG ziL;na^+%IfPRS(`B9v`KwH|hcEXRZ)4duhK5zw)Y z5sWz7ajTR$ZEF*~rXP?@r`1Q8H>q8z?Z!hT%@x}a1-R&O2Wnb2QKyC4GL+cx$S~n;Q8gMJmQP0pVkvndH}{FI~|MBh}R&iJqk4G0o^{ z&8<`Fl{V)@WI`g3(`F#0J=wVcrRc^6o=x7f$x8wdi;{=dEln zM4xJB#N=o#Yy(6QtYO(6hSxS*HrZk3!jq(%*B9sJGXs=_6WCP6cOm&LFJJ<`Lp*JP~Kf7brC`4MqR*ws@S7NX?>z34zel%=W_ zpa!Y2mXhi=lC81U04_#7FSjotPkNM`#JIuN<1mrn93?>ahz_{^%3M4W!E7}>b_CTc zI1B1IdrE66yYiR99zw#yrH0*gf%XG6BQi4?PyrSM!;A+3Ii|s*VU>?>x9-08L7$sn zeNW=fvs5K}s{3M9*y2Ny`!nA|O6sU92n$4gF=~tV0 z5IN~b`9XH6-rlaNt*xR*HCsKRgrIJf5fYS*X5N<;^l9N z;!z@%q2~#kQR>t2M$r}Hd{APj|GKn(Y1;ZBPZkx;vfS(fzCizBfgo!wzMic0o9-2& zy;$&mYm&he^hpR~tNRST%)1BKcytFhIuWEnQV5^=l%Ap;(_lo$I3BcjylIpi>hdr& zv>zC!Eo14tf^&UPndk-qe{(?V3g0*mx}pS9R5aIMnCZLC;>GfPrE<;ERA{m5eU2~E zjp@SD@S?l;OJAa*=0KzsAt{bV@4<8LJIg#$pWIiEaUVgPvMNrJX41vpRPG%T!6eu*}s7p1Bi3INH zF(XDX%22sRIV-EyXEQW5S4wgy)`WeUdKMBA5LqOu( zy0eJHMNs0u6M=97T;RNADq zt;}HR0y)l6g)j)_`8R^Os;;o=pzlwYM!3KzKezR(*R5#7JK_Ajq?wDys%FCm5+~|{SP#bf9eNf=RG@7_@yq4#`#a3VKm;~XdsT? z`sK{r-`eBk|4ly-CqMshx^S^`{!ZiM{bwJ5xVV0o%f-d_&pril@$kS_%3p2c;{90$ z)ER7L3vmXV4Qp8m*r2B8<^bul^KyXLIXSsF8Gd^{AMI?a4i2!Ha 1 ? self.presentationData.strings.ScheduledMessages_Reminder_DeleteMany : self.presentationData.strings.ScheduledMessages_Reminder_Delete + } else if case .peer(self.context.account.peerId) = self.chatLocation, messages.values.allSatisfy({ message in message?._asMessage().effectivelyIncoming(self.context.account.peerId) ?? false }) { localOptionText = self.presentationData.strings.Chat_ConfirmationRemoveFromSavedMessages } else { localOptionText = self.presentationData.strings.Chat_ConfirmationDeleteFromSavedMessages diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 8929412b4d..653198660e 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3790,6 +3790,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } + public func makeGiftAuctionInfoScreen(context: AccountContext, gift: StarGift, completion: (() -> Void)?) -> ViewController { + return GiftAuctionInfoScreen(context: context, gift: gift, completion: completion) + } + + public func makeGiftAuctionScreen(context: AccountContext, gift: StarGift, auctionContext: GiftAuctionContext) -> ViewController { + return GiftAuctionScreen(context: context, gift: gift, auctionContext: auctionContext) + } + public func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController { let editorSubject: Signal switch subject { @@ -3975,8 +3983,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return ChannelMembersSearchControllerImpl(params: params) } - public func makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController { - return NewContactScreen(context: context, initialData: NewContactScreen.initialData(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber), completion: { _ in }) + public func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController { + return NewContactScreen(context: context, initialData: NewContactScreen.initialData(peer: peer, phoneNumber: phoneNumber), completion: completion) } } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index ee3759df89..03a5bb7d8f 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -728,7 +728,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon if let media { #if DEBUG if !"".isEmpty { - let _ = context.engine.messages.beginStoryLivestream(peerId: context.account.peerId, privacy: result.options.privacy, isForwardingDisabled: false).startStandalone() + let _ = context.engine.messages.beginStoryLivestream(peerId: context.account.peerId, rtmp: true, privacy: result.options.privacy, isForwardingDisabled: false, messagesEnabled: true, sendPaidMessageStars: 0).startStandalone() } #endif diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 30b29382e3..6170910c92 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -100,17 +100,25 @@ final class NetworkBroadcastPartSource: BroadcastPartSource { return (dataSource |> deliverOn(self.queue) - |> mapToSignal { [weak self] dataSource -> Signal in + |> mapToSignal { [weak self] dataSource -> Signal in if let dataSource = dataSource { self?.dataSource = dataSource return engine.calls.requestStreamState(dataSource: dataSource, callId: callId, accessHash: accessHash) + |> mapToSignal { value in + if let value { + return .single(value.channels.first?.latestTimestamp ?? 0) + } else { + return engine.calls.serverTime() + |> map(Optional.init) + } + } } else { return .single(nil) } } |> deliverOn(self.queue)).start(next: { result in - if let channel = result?.channels.first { - completion(channel.latestTimestamp) + if let result { + completion(result) } else { completion(0) }