diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7051fae35d..ff8bcd7404 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14700,6 +14700,9 @@ Sorry for the inconvenience."; "AccessDenied.AgeVerificationCamera" = "Telegram needs access to your camera for age verification.\n\nOpen your device's Settings > Privacy > Camera and set Telegram to ON."; +"PeerInfo.Gifts.Collections.All" = "All Gifts"; +"PeerInfo.Gifts.Collections.Add" = "Add Collection"; + "PeerInfo.Gifts.AddGiftsButton" = "Add Gifts"; "PeerInfo.Gifts.AddCollection" = "Add Collection"; @@ -14728,6 +14731,12 @@ Sorry for the inconvenience."; "PeerInfo.Gifts.EmptyCollection.Text" = "Add some gifts to this collection."; "PeerInfo.Gifts.EmptyCollection.Action" = "Add to Collection"; +"PeerInfo.Gifts.AddedToCollection" = "The gift has been added to **%@**."; +"PeerInfo.Gifts.RemovedFromCollection" = "The gift has been removed from **%@**."; + +"PeerInfo.Gifts.AddedToCollectionUnique" = "**%1$@** has been added to **%2$@**."; +"PeerInfo.Gifts.RemovedFromCollectionUnique" = "**%1$@** has been removed from **%2$@**."; + "AddGifts.Title" = "Add Gifts"; "AddGifts.AddGifts_1" = "Add %@ Gift"; "AddGifts.AddGifts_any" = "Add %@ Gifts"; @@ -14736,8 +14745,8 @@ Sorry for the inconvenience."; "Gift.Options.Gift.BuyLimitReached_any" = "You've already sent %@ of these gifts, and it's the limit."; "AgeVerification.Title" = "Age Verification"; -"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; -"AgeVerification.Text.gb" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"AgeVerification.Text.GB" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; "AgeVerification.Verify" = "Verify My Age"; "AgeVerification.Success.Title" = "Age check passed!"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index cf314b5f76..49af8e8865 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1298,7 +1298,7 @@ public protocol SharedAccountContext: AnyObject { func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController - func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) + func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 95baaa332f..035fa49f99 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -12,6 +12,7 @@ public final class BotCheckoutController: ViewController { public enum FetchError { case generic case disallowedStarGifts + case starGiftsUserLimit } public let form: BotPaymentForm @@ -58,6 +59,8 @@ public final class BotCheckoutController: ViewController { switch error { case .disallowedStarGift: return .disallowedStarGifts + case .starGiftUserLimit: + return .starGiftsUserLimit default: return .generic } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 858ccf4ab0..5b8e949137 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1250,7 +1250,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 6e4a1e43e0..cb4f254746 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -3282,7 +3282,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) interaction.dismissSearch() } @@ -4268,7 +4269,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } } @@ -4287,7 +4289,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) interaction.dismissSearch() } else { diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 8c9f3a2d7b..dcdf83da92 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -871,7 +871,7 @@ if (_intent == TGMediaAssetsControllerSendMediaIntent && _selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; - + return [TGMediaAssetsController resultSignalsForSelectionContext:_selectionContext editingContext:_editingContext intent:_intent currentItem:currentItem storeAssets:storeAssets convertToJpeg:false descriptionGenerator:descriptionGenerator saveEditedPhotos:_saveEditedPhotos]; } @@ -889,6 +889,9 @@ if (selectedItems.count == 0 && currentItem != nil) [selectedItems addObject:currentItem]; + if (intent == TGMediaAssetsControllerSendMediaIntent) + [[NSUserDefaults standardUserDefaults] setObject:@(editingContext.isHighQualityPhoto) forKey:@"TG_photoHighQuality_v0"]; + if (saveEditedPhotos && storeAssets && editingContext != nil) { NSMutableArray *fullSizeSignals = [[NSMutableArray alloc] init]; diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 3eeeb0943b..70114ae942 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2159,6 +2159,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att strongSelf.controllerNode.dismissInput() } }, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext()) + + let highQualityPhoto = UserDefaults.standard.bool(forKey: "TG_photoHighQuality_v0") + if highQualityPhoto { + self.interaction?.editingState.setHighQualityPhoto(highQualityPhoto) + } + self.interaction?.selectionState?.grouping = true self.interaction?.editingState.sendPaidMessageStars = sendPaidMessageStars ?? 0 diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 76fba73534..e0961e864b 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3096,13 +3096,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { products.append(PremiumProduct(option: option, storeProduct: product)) } } - - //TODO:release - if let product = availableProducts.first(where: { $0.id.hasSuffix(".annual") }) { - let (currency, price) = product.priceCurrencyAndAmount - products.insert(PremiumProduct(option: PremiumPromoConfiguration.PremiumProductOption(isCurrent: false, months: 24, currency: currency, amount: price * 2, botUrl: "", transactionId: nil, availableForUpgrade: true, storeProductId: "org.telegram.telegramPremium.biannual"), storeProduct: product), at: 0) - } - + strongSelf.products = products strongSelf.isPremium = forceHasPremium || isPremium strongSelf.otherPeerName = otherPeerName diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 9330c8db39..173c4b50c5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -180,6 +180,7 @@ public enum BotPaymentFormRequestError { case noPaymentNeeded case disallowedStarGift case starGiftResellTooEarly(Int32) + case starGiftUserLimit } extension BotPaymentInvoice { @@ -488,6 +489,8 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw if let value = Int32(timeout) { return .fail(.starGiftResellTooEarly(value)) } + } else if error.errorDescription == "STARGIFT_USER_USAGE_LIMITED" { + return .fail(.starGiftUserLimit) } return .fail(.generic) } @@ -651,6 +654,7 @@ public enum SendBotPaymentFormError { case alreadyPaid case starGiftOutOfStock case disallowedStarGift + case starGiftUserLimit } public enum SendBotPaymentResult { diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index efb8d18c65..03dbf571ac 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -405,26 +405,81 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi if state.verificationPassed { completion() } else { + let miniappPromise = Promise(nil) + var useVerifyAgeBot = false + if let value = context.currentAppConfiguration.with({ $0 }).data?["force_verify_age_bot"] as? Bool, value { + useVerifyAgeBot = value + } + if useVerifyAgeBot, let verifyAgeBotUsername = context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { + miniappPromise.set(context.engine.peers.resolvePeerByName(name: verifyAgeBotUsername, referrer: nil) + |> mapToSignal { result in + if case let .result(peer) = result { + return .single(peer) + } + return .complete() + }) + } let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability in if check { - let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { [weak parentController] passed in - if passed { - let _ = updateAgeVerificationState(engine: context.engine, { _ in - return AgeVerificationState(verificationPassed: passed) - }).start() - completion() - - let navigationController = parentController?.navigationController - Queue.mainQueue().after(2.0) { - let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true }) - (navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .window(.root)) - } + var requiredAge = 18 + if let value = context.currentAppConfiguration.with({ $0 }).data?["verify_age_min"] as? Double { + requiredAge = Int(value) + } + + let success = { [weak parentController] in + let _ = updateAgeVerificationState(engine: context.engine, { _ in + return AgeVerificationState(verificationPassed: true) + }).start() + completion() + + let navigationController = parentController?.navigationController + Queue.mainQueue().after(2.0) { + let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true }) + (navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .current) + } + } + + let failure = { [weak parentController] in + let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true }) + parentController?.present(controller, in: .current) + } + + let _ = (miniappPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { peer in + if let peer, let parentController { + context.sharedContext.openWebApp( + context: context, + parentController: parentController, + updatedPresentationData: nil, + botPeer: peer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: true, + payload: nil, + verifyAgeCompletion: { age in + if age >= requiredAge { + success() + } else { + failure() + } + } + ) } else { - let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true }) - parentController?.present(controller, in: .window(.root)) + let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in + if age >= requiredAge { + success() + } else { + failure() + } + }) + parentController?.push(scanScreen) } }) - parentController?.push(scanScreen) } }) parentController?.push(infoScreen) diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift index 334469152d..66d5ad7bc3 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift @@ -20,8 +20,6 @@ import ZipArchive import PlainButtonComponent import MultilineTextComponent -private let requiredAge = 18 - final class FaceScanScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -109,7 +107,7 @@ final class FaceScanScreenComponent: Component { self.backgroundColor = .black - self.previewLayer.backgroundColor = UIColor.red.cgColor + //self.previewLayer.backgroundColor = UIColor.red.cgColor self.previewLayer.videoGravity = .resizeAspectFill self.layer.addSublayer(previewLayer) @@ -240,7 +238,7 @@ final class FaceScanScreenComponent: Component { let targetCenter = CGPoint(x: 0.5, y: 0.5) let distance = sqrt(pow(faceCenter.x - targetCenter.x, 2) + pow(faceCenter.y - targetCenter.y, 2)) - if distance < 0.35 { + if distance < 0.24 { switch processState { case .waitingForFace: self.processState = .positioning @@ -320,7 +318,7 @@ final class FaceScanScreenComponent: Component { if !self.ages.isEmpty { let averageAge = self.ages.reduce(0, +) / Double(self.ages.count) if let environment = self.environment, let controller = environment.controller() as? FaceScanScreen { - controller.completion(averageAge >= Double(requiredAge)) + controller.completion(Int(averageAge)) controller.dismiss(animated: true) } } else { @@ -436,7 +434,7 @@ final class FaceScanScreenComponent: Component { let center = CGPoint(x: availableSize.width / 2, y: environment.statusBarHeight + 10.0 + widthRadius * 1.3) - var previewScale = 1.0 + var previewScale = 0.85 if self.processState == .tracking || self.processState == .readyToStart || self.processState == .completed || self.transitioningToViewFinder { let circlePath = CGPath(roundedRect: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2), cornerWidth: radius, cornerHeight: radius, transform: nil) path.addPath(circlePath) @@ -457,7 +455,6 @@ final class FaceScanScreenComponent: Component { self.frameView.frame = frameViewFrame self.frameView.update(size: frameViewFrame.size) - //TODO:localize var instructionString = environment.strings.FaceScan_Instruction_Position switch self.processState { case .waitingForFace, .positioning: @@ -545,12 +542,12 @@ final class FaceScanScreenComponent: Component { public final class FaceScanScreen: ViewControllerComponentContainer { private let context: AccountContext - fileprivate let completion: (Bool) -> Void + fileprivate let completion: (Int) -> Void public init( context: AccountContext, availability: Signal, - completion: @escaping (Bool) -> Void + completion: @escaping (Int) -> Void ) { self.context = context self.completion = completion diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index eec3b4716c..fd33e2dc21 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -363,6 +363,8 @@ final class GiftSetupScreenComponent: Component { let entities = generateChatInputTextEntities(self.textInputState.text) var finalPrice: Int64 + var perUserLimit: Int32? + var giftFile: TelegramMediaFile? let source: BotPaymentInvoiceSource switch component.subject { case let .premium(product): @@ -377,6 +379,8 @@ final class GiftSetupScreenComponent: Component { if self.includeUpgrade, let upgradeStars = starGift.upgradeStars { finalPrice += upgradeStars } + perUserLimit = starGift.perUserLimit?.total + giftFile = starGift.file source = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities) } @@ -395,6 +399,8 @@ final class GiftSetupScreenComponent: Component { switch error { case .disallowedStarGifts: return .fail(.disallowedStarGift) + case .starGiftsUserLimit: + return .fail(.starGiftUserLimit) default: return .fail(.generic) } @@ -468,6 +474,14 @@ final class GiftSetupScreenComponent: Component { var errorText: String? switch error { + case .starGiftUserLimit: + if let perUserLimit, let giftFile { + let text = presentationData.strings.Gift_Options_Gift_BuyLimitReached(perUserLimit) + let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: giftFile, loop: true, title: nil, text: text, undoText: nil, customAction: nil), action: { _ in return false }) + controller.present(undoController, in: .current) + return + } + return case .starGiftOutOfStock: errorText = presentationData.strings.Gift_Send_ErrorOutOfStock case .disallowedStarGift: diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 3f2bb8baec..9ff2dc3ff3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -288,11 +288,14 @@ private final class GiftViewSheetContent: CombinedComponent { } var minRequiredAmount = StarsAmount(value: 100, nanos: 0) + var canUpgrade = false if let resellStars = self.subject.arguments?.resellStars { minRequiredAmount = StarsAmount(value: resellStars, nanos: 0) + } else if let arguments = self.subject.arguments, arguments.canUpgrade && arguments.upgradeStars == nil { + canUpgrade = true } - if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount { + if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount || canUpgrade { self.optionsDisposable = (context.engine.payments.starsTopUpOptions() |> deliverOnMainQueue).start(next: { [weak self] options in guard let self else { diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index f784de7564..6acf5283b7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -1257,7 +1257,8 @@ final class AffiliateProgramSetupScreenComponent: Component { simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) } else if let navigationController = controller.navigationController as? NavigationController { component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(item.peer), subject: nil, keepStack: .always, animated: true, pushController: { [weak navigationController] chatController, animated, completion in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 42bec125e2..794b13e9e7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -1917,7 +1917,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight var subtitleRatingSize: CGSize? - if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating { + if !"".isEmpty, let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating { let subtitleRating: ComponentView var subtitleRatingTransition = ComponentTransition(transition) if let current = self.subtitleRating { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index a2b2b6ddc5..7ae3a0bd34 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1451,7 +1451,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese simple: true, source: .generic, skipTermsOfService: true, - payload: nil + payload: nil, + verifyAgeCompletion: nil ) }) } @@ -11365,7 +11366,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let canReorderEquals = lhs.2 == rhs.2 return filterEquals && sortingEquals && canReorderEquals }) - |> map { [weak self, weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in + |> map { [weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in var items: [ContextMenuItem] = [] if hasVisibility { @@ -11390,13 +11391,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - //TODO:release - self?.openShareLink(url: "https://t.me/") - }))) +// items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) +// }, action: { [weak self] _, f in +// f(.default) +// self?.openShareLink(url: "https://t.me/") +// }))) } if canReorder { @@ -11428,15 +11428,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.separator) } - items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) - }, action: { [weak giftsContext] _, f in - f(.default) - - giftsContext?.updateSorting(sorting == .date ? .value : .date) - }))) + if let pane, case .all = pane.currentCollection { + items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) + }, action: { [weak giftsContext] _, f in + f(.default) + + giftsContext?.updateSorting(sorting == .date ? .value : .date) + }))) - items.append(.separator) + items.append(.separator) + } let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in var updatedFilter = filter diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index b1ec546109..63383fd9ea 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -23,17 +23,20 @@ final class AddGiftsScreenComponent: Component { let context: AccountContext let peerId: EnginePeer.Id let collectionId: Int32 + let remainingCount: Int32 let profileGifts: ProfileGiftsContext init( context: AccountContext, peerId: EnginePeer.Id, collectionId: Int32, + remainingCount: Int32, profileGifts: ProfileGiftsContext ) { self.context = context self.peerId = peerId self.collectionId = collectionId + self.remainingCount = remainingCount self.profileGifts = profileGifts } @@ -111,6 +114,7 @@ final class AddGiftsScreenComponent: Component { var contentSize = CGSize(width: self.scrollView.bounds.width, height: contentHeight) contentSize.height += environment.safeInsets.bottom contentSize.height = max(contentSize.height, self.scrollView.bounds.size.height) + contentSize.height += 50.0 + 24.0 transition.setFrame(view: giftsListView, frame: CGRect(origin: CGPoint(), size: contentSize)) if self.scrollView.contentSize != contentSize { @@ -128,7 +132,13 @@ final class AddGiftsScreenComponent: Component { if let current = self.giftsListView { giftsListView = current } else { - giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId) + giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId, remainingSelectionCount: component.remainingCount) + giftsListView.onContentUpdated = { [weak self] in + guard let self else { + return + } + self.state?.updated(transition: .immediate) + } giftsListView.selectionUpdated = { [weak self] in guard let self else { return @@ -248,6 +258,7 @@ public final class AddGiftsScreen: ViewControllerComponentContainer { context: AccountContext, peerId: EnginePeer.Id, collectionId: Int32, + remainingCount: Int32, completion: @escaping ([ProfileGiftsContext.State.StarGift]) -> Void ) { self.context = context @@ -264,10 +275,10 @@ public final class AddGiftsScreen: ViewControllerComponentContainer { context: context, peerId: peerId, collectionId: collectionId, + remainingCount: remainingCount, profileGifts: self.profileGifts ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) - self.title = presentationData.strings.AddGifts_Title self.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index ef195fb43d..2a52b933bc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -36,6 +36,7 @@ final class GiftsListView: UIView { private let canSelect: Bool private let ignoreCollection: Int32? + private let remainingSelectionCount: Int32 private var dataDisposable: Disposable? @@ -124,13 +125,14 @@ final class GiftsListView: UIView { var contextAction: ((ProfileGiftsContext.State.StarGift, UIView, ContextGesture) -> Void)? var addToCollection: (() -> Void)? - init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil) { + init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil, remainingSelectionCount: Int32 = 0) { self.context = context self.peerId = peerId self.profileGifts = profileGifts self.giftsCollections = giftsCollections self.canSelect = canSelect self.ignoreCollection = ignoreCollection + self.remainingSelectionCount = remainingSelectionCount if let value = context.currentAppConfiguration.with({ $0 }).data?["stargifts_pinned_to_top_limit"] as? Double { self.maxPinnedCount = Int(value) @@ -406,6 +408,13 @@ final class GiftsListView: UIView { } return self.updateScrolling(interactive: interactive, topInset: topInset, visibleBounds: visibleBounds, transition: transition) } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if let topInset = self.topInset, point.y < topInset { + return false + } + return super.point(inside: point, with: event) + } func updateScrolling(interactive: Bool = false, topInset: CGFloat, visibleBounds: CGRect, transition: ComponentTransition) -> CGFloat { self.topInset = topInset @@ -548,8 +557,10 @@ final class GiftsListView: UIView { if self.selectedItemIds.contains(itemReferenceId) { self.selectedItemIds.remove(itemReferenceId) } else { - self.selectedItemIds.insert(itemReferenceId) - self.selectedItemsMap[itemReferenceId] = product + if self.selectedItemIds.count < self.remainingSelectionCount { + self.selectedItemIds.insert(itemReferenceId) + self.selectedItemsMap[itemReferenceId] = product + } } self.selectionUpdated() self.updateScrolling(transition: .easeInOut(duration: 0.25)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index ab1595fa59..219bcc9a2d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -151,6 +151,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr super.init() + self.giftsListView.onContentUpdated = { [weak self] in + guard let self else { + return + } + if let params = self.currentParams { + self.update(size: params.size, topInset: params.topInset, sideInset: params.sideInset, bottomInset: params.bottomInset, deviceMetrics: params.deviceMetrics, visibleHeight: params.visibleHeight, isScrollingLockedAtTop: params.isScrollingLockedAtTop, expandProgress: params.expandProgress, navigationHeight: params.navigationHeight, presentationData: params.presentationData, synchronous: true, transition: .immediate) + } + } + self.addSubnode(self.backgroundNode) self.addSubnode(self.scrollNode) @@ -184,7 +193,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.scrollNode.view.contentInsetAdjustmentBehavior = .never self.scrollNode.view.delegate = self - self.scrollNode.view.insertSubview(self.giftsListView, at: 0) + if let tabSelectorView = self.tabSelector.view { + self.scrollNode.view.insertSubview(self.giftsListView, aboveSubview: tabSelectorView) + } else { + self.scrollNode.view.insertSubview(self.giftsListView, at: 0) + } } private func item(at point: CGPoint) -> (AnyHashable, ComponentView)? { @@ -201,7 +214,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } @@ -211,6 +224,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } if let collection { self.setCurrentCollection(collection: .collection(collection.id)) + + if let tabSelectorView = self.tabSelector.view as? TabSelectorComponent.View { + tabSelectorView.scrollToEnd() + } } }) }) @@ -230,6 +247,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self?.setCurrentCollection(collection: .all) let _ = self?.profileGiftsCollections.deleteCollection(id: id).start() + + if let tabSelectorView = self?.tabSelector.view as? TabSelectorComponent.View { + tabSelectorView.scrollToStart() + } }) ]), ActionSheetItemGroup(items: [ @@ -242,7 +263,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } public func addGiftsToCollection(id: Int32) { - let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, completion: { [weak self] gifts in + var collectionGiftsMaxCount: Int32 = 1000 + if let value = self.context.currentAppConfiguration.with({ $0 }).data?["stargifts_collection_gifts_limit"] as? Double { + collectionGiftsMaxCount = Int32(value) + } + var remainingCount = collectionGiftsMaxCount + if let currentCount = self.giftsListView.profileGifts.currentState?.count { + remainingCount = max(0, collectionGiftsMaxCount - currentCount) + } + let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, remainingCount: remainingCount, completion: { [weak self] gifts in guard let self else { return } @@ -256,7 +285,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } @@ -460,20 +489,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } f(.default) - self.renameCollection(id: id) + Queue.mainQueue().after(0.15) { + self.renameCollection(id: id) + } }))) - items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) - }, action: { [weak self] _, f in - guard let self else { - return - } - f(.default) - - //TODO:release - let _ = self - }))) +// items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in +// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) +// }, action: { [weak self] _, f in +// guard let self else { +// return +// } +// f(.default) +// +// let _ = self +// }))) items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_Reorder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor) @@ -494,7 +524,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } f(.default) - self.deleteCollection(id: id) + Queue.mainQueue().after(0.15) { + self.deleteCollection(id: id) + } }))) let contextController = ContextController( @@ -515,13 +547,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr var topInset: CGFloat = 60.0 - if let collections = self.collections, !collections.isEmpty { + var canEditCollections = false + if self.peerId == self.context.account.peerId || self.canManage { + canEditCollections = true + } + + let hasNonEmptyCollections = self.collections?.contains(where: { $0.count > 0 }) ?? false + if let collections = self.collections, !collections.isEmpty && (hasNonEmptyCollections || canEditCollections) { var tabSelectorItems: [TabSelectorComponent.Item] = [] tabSelectorItems.append(TabSelectorComponent.Item( id: AnyHashable(GiftCollection.all.rawValue), - title: "All Gifts" + title: params.presentationData.strings.PeerInfo_Gifts_Collections_All )) - + var effectiveCollections: [StarGiftCollection] = collections if let reorderedCollectionIds = self.reorderedCollectionIds { var collectionMap: [Int32: StarGiftCollection] = [:] @@ -538,6 +576,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } for collection in effectiveCollections { + if !canEditCollections && collection.count == 0 { + continue + } tabSelectorItems.append(TabSelectorComponent.Item( id: AnyHashable(GiftCollection.collection(collection.id).rawValue), content: .component(AnyComponent( @@ -557,19 +598,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } )) } - - tabSelectorItems.append(TabSelectorComponent.Item( - id: AnyHashable(GiftCollection.create.rawValue), - content: .component(AnyComponent( - CollectionTabItemComponent( - context: self.context, - icon: .add, - title: "Add Collection", - theme: params.presentationData.theme - ) - )), - isReorderable: false - )) + + if canEditCollections { + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(GiftCollection.create.rawValue), + content: .component(AnyComponent( + CollectionTabItemComponent( + context: self.context, + icon: .add, + title: params.presentationData.strings.PeerInfo_Gifts_Collections_Add, + theme: params.presentationData.theme + ) + )), + isReorderable: false + )) + } let tabSelectorSize = self.tabSelector.update( transition: transition, @@ -621,7 +664,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if let tabSelectorView = self.tabSelector.view { if tabSelectorView.superview == nil { tabSelectorView.alpha = 1.0 - self.scrollNode.view.addSubview(tabSelectorView) + self.scrollNode.view.insertSubview(tabSelectorView, at: 0) if !transition.animation.isImmediate { tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -938,10 +981,52 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr }, iconPosition: collection.icon == nil ? .left : .right, action: { [weak self] _, f in f(.default) + guard let self else { + return + } + if isAdded, let giftReference = gift.reference { - let _ = self?.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start() + let _ = self.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start() } else { - let _ = self?.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start() + let _ = self.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start() + } + + var giftFile: TelegramMediaFile? + var giftTitle: String? + switch gift.gift { + case let .generic(gift): + giftFile = gift.file + case let .unique(uniqueGift): + giftTitle = uniqueGift.title + " #\(presentationStringsFormattedNumber(uniqueGift.number, currentParams.presentationData.dateTimeFormat.groupingSeparator))" + for attribute in uniqueGift.attributes { + if case let .model(_, file, _) = attribute { + giftFile = file + } + } + } + + if let giftFile { + let text: String + if let giftTitle { + if isAdded { + text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollectionUnique(giftTitle, collection.title).string + } else { + text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollectionUnique(giftTitle, collection.title).string + } + } else { + if isAdded { + text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollection(collection.title).string + } else { + text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollection(collection.title).string + } + } + + let undoController = UndoOverlayController( + presentationData: currentParams.presentationData, + content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil), + action: { _ in return true } + ) + self.parentController?.present(undoController, in: .current) } }))) } @@ -1351,14 +1436,12 @@ private final class CollectionTabItemComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: .white)) + text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: component.theme.list.itemSecondaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) ) - - let tintColor = component.theme.list.itemSecondaryTextColor - + var iconOffset: CGFloat = 0.0 var iconSize = CGSize() if let icon = component.icon { @@ -1392,7 +1475,7 @@ private final class CollectionTabItemComponent: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: "Chat/Input/Media/PanelBadgeAdd", - tintColor: tintColor + tintColor: component.theme.list.itemSecondaryTextColor )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -1428,8 +1511,6 @@ private final class CollectionTabItemComponent: Component { self.addSubview(titleView) } titleView.frame = titleFrame - - transition.setTintColor(layer: titleView.layer, color: tintColor) } let size: CGSize diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index 53502dd20c..ef0827e734 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -483,6 +483,14 @@ public final class TabSelectorComponent: Component { } } + public func scrollToStart() { + self.setContentOffset(.zero, animated: true) + } + + public func scrollToEnd() { + self.setContentOffset(CGPoint(x: self.contentSize.width - self.bounds.width, y: 0.0), animated: true) + } + func update(component: TabSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let selectionColorUpdated = component.colors.selection != self.component?.colors.selection diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 51b4902c1e..52bfd8774d 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -900,7 +900,21 @@ final class AuthorizedApplicationContext { } if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController { - self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: parentController, + updatedPresentationData: nil, + botPeer: peer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: true, + payload: nil, + verifyAgeCompletion: nil + ) } else { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: alwaysKeepMessageId || isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil)) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift index 36860380ad..e2dcfb2857 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift @@ -128,8 +128,7 @@ extension ChatControllerImpl { } } - if "".isEmpty { - //TODO:release + if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ReplyToItem, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] c, _ in diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 312ce0b606..083f47e8d5 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -27,7 +27,8 @@ func openWebAppImpl( simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, - payload: String? + payload: String?, + verifyAgeCompletion: ((Int) -> Void)? ) { if context.isFrozen { parentController.push(context.sharedContext.makeAccountFreezeInfoScreen(context: context)) @@ -246,7 +247,7 @@ func openWebAppImpl( navigationController = parentController.effectiveNavigationController } return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) - }) + }, verifyAgeCompletion: verifyAgeCompletion) controller.navigationPresentation = .flatModal parentController.push(controller) @@ -351,7 +352,21 @@ public extension ChatControllerImpl { } self.chatDisplayNode.dismissInput() - self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: EnginePeer(peer), chatPeer: EnginePeer(peer), threadId: self.chatLocation.threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: false, payload: nil) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: self, + updatedPresentationData: self.updatedPresentationData, + botPeer: EnginePeer(peer), + chatPeer: EnginePeer(peer), + threadId: self.chatLocation.threadId, + buttonText: buttonText, + url: url, + simple: simple, + source: source, + skipTermsOfService: false, + payload: nil, + verifyAgeCompletion: nil + ) } fileprivate static func botRequestSwitchInline(context: AccountContext, controller: ChatControllerImpl?, peerId: EnginePeer.Id, botAddress: String, query: String, chatTypes: [ReplyMarkupButtonRequestPeerType]?, completion: @escaping () -> Void) -> Void { @@ -616,7 +631,21 @@ public extension ChatControllerImpl { } }) } else { - context.sharedContext.openWebApp(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: payload) + context.sharedContext.openWebApp( + context: context, + parentController: parentController, + updatedPresentationData: updatedPresentationData, + botPeer: botPeer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: false, + payload: payload, + verifyAgeCompletion: nil + ) } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 82a1c85707..d1d891b2c3 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -8907,7 +8907,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G commit() }) } else { - self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: botAppStart.payload) + self.context.sharedContext.openWebApp( + context: self.context, + parentController: self, + updatedPresentationData: self.updatedPresentationData, + botPeer: peer, + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: false, + payload: botAppStart.payload, + verifyAgeCompletion: nil + ) commit() } }) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 57f998a350..9f4b2f0fe6 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3882,8 +3882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return incomingMessagePrivacyScreen(context: context, value: value, exceptions: exceptions, update: update) } - public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) { - openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload) + public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) { + openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload, verifyAgeCompletion: verifyAgeCompletion) } public func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 61f720d22d..8d062984b6 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -31,7 +31,6 @@ import PeerInfoScreen import PeerInfoStoryGridScreen import ShareWithPeersScreen import ChatEmptyNode -//import FaceScanScreen import UndoUI private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { @@ -237,22 +236,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon self.accountSettingsController = accountSettingsController self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) - -// Queue.mainQueue().after(1.0) { -// let context = self.context -// let infoScreen = AgeVerificationScreen(context: context, completion: { [weak chatListController] proceed in -// if proceed { -// let scanScreen = FaceScanScreen(context: context, completion: { success in -// let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: "Age check passed!", text: "You can now view this content.", cancel: nil, destructive: false), elevatedLayout: true, action: { _ in return true }) -// Queue.mainQueue().after(0.1) { -// chatListController?.present(controller, in: .window(.root)) -// } -// }) -// chatListController?.push(scanScreen) -// } -// }) -// chatListController.push(infoScreen) -// } } public func updateRootControllers(showCallsTab: Bool) { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 3445a10613..2d92f8bbe6 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -653,10 +653,14 @@ public final class WebAppController: ViewController, AttachmentContainable { self.animateTransitionIn() }) } - + @available(iOSApplicationExtension 15.0, iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { - decisionHandler(.prompt) + if self.controller?.isVerifyAgeBot == true && type == .camera { + decisionHandler(.grant) + } else { + decisionHandler(.prompt) + } } func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { @@ -1756,6 +1760,12 @@ public final class WebAppController: ViewController, AttachmentContainable { } case "web_app_hide_keyboard": self.view.window?.endEditing(true) + case "web_app_verify_age": + if let json, self.controller?.isVerifyAgeBot == true { + if let ageValue = json["age"] as? Double { + self.controller?.verifyAgeCompletion?(Int(ageValue)) + } + } default: break } @@ -3281,6 +3291,8 @@ public final class WebAppController: ViewController, AttachmentContainable { public var completion: () -> Void = {} public var requestSwitchInline: (String, [ReplyMarkupButtonRequestPeerType]?, @escaping () -> Void) -> Void = { _, _, _ in } + public var verifyAgeCompletion: ((Int) -> Void)? + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { self.context = context self.source = params.source @@ -3323,16 +3335,20 @@ public final class WebAppController: ViewController, AttachmentContainable { self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) self.navigationItem.leftBarButtonItem?.target = self - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if !self.isVerifyAgeBot { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme) - titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified) - self.navigationItem.titleView = titleView - self.titleView = titleView + if !self.isVerifyAgeBot { + let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme) + titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified) + self.navigationItem.titleView = titleView + self.titleView = titleView + } self.moreButtonNode.action = { [weak self] _, gesture in if let strongSelf = self { @@ -3387,6 +3403,13 @@ public final class WebAppController: ViewController, AttachmentContainable { self.presentationDataDisposable?.dispose() } + private var isVerifyAgeBot: Bool { + if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { + return self.botAddress == ageBotUsername + } + return false + } + public func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) { switch self.source { case .generic, .settings: @@ -3857,7 +3880,8 @@ public func standaloneWebAppController( willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, - getSourceRect: (() -> CGRect?)? = nil + getSourceRect: (() -> CGRect?)? = nil, + verifyAgeCompletion: ((Int) -> Void)? = nil ) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.source == .menu, hasTextInput: false, isFullSize: params.fullSize, makeEntityInputView: { return nil @@ -3868,6 +3892,7 @@ public func standaloneWebAppController( webAppController.completion = completion webAppController.getNavigationController = getNavigationController webAppController.requestSwitchInline = requestSwitchInline + webAppController.verifyAgeCompletion = verifyAgeCompletion present(webAppController, webAppController.mediaPickerContext) } controller.willDismiss = willDismiss diff --git a/versions.json b/versions.json index e4f292734f..aaa701a8e4 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.14", + "app": "11.13.3", "xcode": "16.2", "bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217", "macos": "15"