From 9d703f5b60460b62256f7c50d0aad9e7b2554ece Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Jul 2025 00:38:47 +0200 Subject: [PATCH] Various fixes --- .../Sources/AccountContext.swift | 2 +- .../Sources/ChatListController.swift | 3 +- .../Sources/ChatListSearchListPaneNode.swift | 9 ++- .../Sources/AgeVerificationScreen.swift | 80 +++++++++++++++---- .../Sources/FaceScanScreen.swift | 19 ++--- .../Sources/AffiliateProgramSetupScreen.swift | 3 +- .../Sources/PeerInfoScreen.swift | 3 +- .../Sources/PeerInfoGiftsPaneNode.swift | 4 +- .../Sources/ApplicationContext.swift | 16 +++- .../Chat/ChatControllerOpenWebApp.swift | 37 ++++++++- .../TelegramUI/Sources/ChatController.swift | 16 +++- .../Sources/SharedAccountContext.swift | 4 +- .../WebUI/Sources/WebAppController.swift | 45 ++++++++--- versions.json | 2 +- 14 files changed, 188 insertions(+), 55 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 7b0b0d692f..bdbab5249c 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1296,7 +1296,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/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/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index bcf91273b2..03dbf571ac 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -405,6 +405,20 @@ 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 { var requiredAge = 18 @@ -412,24 +426,60 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi requiredAge = Int(value) } - let scanScreen = FaceScanScreen(context: context, availability: availability, requiredAge: requiredAge, 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)) - } + 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 2be0e9db59..66d5ad7bc3 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/FaceScanScreen.swift @@ -25,16 +25,13 @@ final class FaceScanScreenComponent: Component { let context: AccountContext let availability: Signal - let requiredAge: Int init( context: AccountContext, - availability: Signal, - requiredAge: Int + availability: Signal ) { self.context = context self.availability = availability - self.requiredAge = requiredAge } static func ==(lhs: FaceScanScreenComponent, rhs: FaceScanScreenComponent) -> Bool { @@ -307,7 +304,7 @@ final class FaceScanScreenComponent: Component { } private func fillSegment(_ segmentIndex: Int) { - guard let component = self.component, !self.completedAngles.contains(segmentIndex) else { + guard !self.completedAngles.contains(segmentIndex) else { return } self.completedAngles.insert(segmentIndex) @@ -321,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(component.requiredAge)) + controller.completion(Int(averageAge)) controller.dismiss(animated: true) } } else { @@ -437,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) @@ -545,21 +542,19 @@ 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, - requiredAge: Int, - completion: @escaping (Bool) -> Void + completion: @escaping (Int) -> Void ) { self.context = context self.completion = completion super.init(context: context, component: FaceScanScreenComponent( context: context, - availability: availability, - requiredAge: requiredAge + availability: availability ), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil) self.title = "" 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/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 8987ea9da8..557f3ff470 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 ) }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index a736019aad..3011f23546 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -201,7 +201,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 } @@ -264,7 +264,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 } 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/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 48ad0ae251..dcb2d5e440 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/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 059b1bfd9a..03d9138921 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.14", + "app": "11.13.3", "xcode": "16.2", "bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3", "macos": "15"