diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 259199b5f5..8c435029be 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -506,6 +506,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { let containerFrame: CGRect let clipFrame: CGRect let containerScale: CGFloat + + let isFullscreen = controllers.last?.isFullscreen == true if case .compact = layout.metrics.widthClass { self.clipNode.clipsToBounds = true @@ -524,7 +526,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { } var containerTopInset: CGFloat - if isLandscape || controllers.last?.isFullscreen == true { + if isLandscape || isFullscreen { containerTopInset = 0.0 containerLayout = layout @@ -560,7 +562,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height) } } else { - containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) + containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: isFullscreen ? layout.statusBarHeight : nil, inputHeight: isFullscreen ? layout.inputHeight : nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size) containerScale = 1.0 diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 10730e317f..369765b7a5 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -1057,50 +1057,57 @@ public class AttachmentController: ViewController, MinimizableController { var containerLayout = layout let containerRect: CGRect if case .regular = layout.metrics.widthClass { - let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0 - - let size = CGSize(width: 390.0, height: min(620.0, availableHeight)) - - let insets = layout.insets(options: [.input]) - let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) - - let position: CGPoint - let positionY = layout.size.height - size.height - insets.bottom - 40.0 - if let sourceRect = controller.getSourceRect?() { - position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height)) + if controller.isFullscreen { + containerRect = CGRect(origin: .zero, size: layout.size) + self.wrapperNode.cornerRadius = 0.0 + self.wrapperNode.view.mask = nil + self.shadowNode.alpha = 0.0 } else { - position = CGPoint(x: masterWidth - 174.0, y: positionY) - } - - if controller.isStandalone && !controller.forceSourceRect { - var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0) - if let inputHeight = layout.inputHeight, inputHeight > 88.0 { - containerY = layout.size.height - inputHeight - size.height - 80.0 + let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0 + + let size = CGSize(width: 390.0, height: min(620.0, availableHeight)) + + let insets = layout.insets(options: [.input]) + let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) + + let position: CGPoint + let positionY = layout.size.height - size.height - insets.bottom - 40.0 + if let sourceRect = controller.getSourceRect?() { + position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height)) + } else { + position = CGPoint(x: masterWidth - 174.0, y: positionY) + } + + if controller.isStandalone && !controller.forceSourceRect { + var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0) + if let inputHeight = layout.inputHeight, inputHeight > 88.0 { + containerY = layout.size.height - inputHeight - size.height - 80.0 + } + containerRect = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: containerY), size: size) + } else { + containerRect = CGRect(origin: position, size: size) + } + containerLayout.size = containerRect.size + containerLayout.intrinsicInsets.bottom = 12.0 + containerLayout.inputHeight = nil + + if controller.isStandalone { + self.wrapperNode.cornerRadius = 10.0 + } else if self.wrapperNode.view.mask == nil { + let maskView = UIImageView() + maskView.image = generateMaskImage() + maskView.contentMode = .scaleToFill + self.wrapperNode.view.mask = maskView + } + + if let maskView = self.wrapperNode.view.mask { + transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size)) + } + + self.shadowNode.alpha = 1.0 + if self.shadowNode.image == nil { + self.shadowNode.image = generateShadowImage() } - containerRect = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: containerY), size: size) - } else { - containerRect = CGRect(origin: position, size: size) - } - containerLayout.size = containerRect.size - containerLayout.intrinsicInsets.bottom = 12.0 - containerLayout.inputHeight = nil - - if controller.isStandalone { - self.wrapperNode.cornerRadius = 10.0 - } else if self.wrapperNode.view.mask == nil { - let maskView = UIImageView() - maskView.image = generateMaskImage() - maskView.contentMode = .scaleToFill - self.wrapperNode.view.mask = maskView - } - - if let maskView = self.wrapperNode.view.mask { - transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size)) - } - - self.shadowNode.alpha = 1.0 - if self.shadowNode.image == nil { - self.shadowNode.image = generateShadowImage() } } else { let containerHeight: CGFloat diff --git a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift index cb37b98221..a4c2d498bb 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift @@ -167,6 +167,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { let sideInset: CGFloat = params.leftInset + 16.0 let rightInset: CGFloat = sideInset + 24.0 + var titleRightInset = rightInset let verticalInset: CGFloat = 9.0 var spacing: CGFloat = 0.0 @@ -214,6 +215,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { titleString = titleStringValue textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + titleRightInset = sideInset case let .premiumRestore(discount): let discountString = "\(discount)%" let rawTitleString = item.strings.ChatList_PremiumRestoreDiscountTitle(discountString) @@ -291,7 +293,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { leftInset += avatarsWidth + 4.0 } - let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18)) + let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - titleRightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18)) let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 10, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index b26da72544..34dc31be09 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1545,23 +1545,33 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if let _ = user.botInfo { //TODO:localize - items[.permissions]!.append(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO")) var canManageEmojiStatus = false if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) { canManageEmojiStatus = true } - items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: "Emoji Status", value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in - let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value) - |> deliverOnMainQueue).startStandalone() - })) + + if canManageEmojiStatus || data.webAppPermissions?.emojiStatus?.isRequested == true { + items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: "Emoji Status", value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in + let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value) + |> deliverOnMainQueue).startStandalone() + + let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in + return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) + }.startStandalone() + })) + } if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true { items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: "Geolocation", value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in - return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value)) + return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value), emojiStatus: current?.emojiStatus) }.startStandalone() })) } + + if !items[.permissions]!.isEmpty { + items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO"), at: 0) + } } if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index ca023a213e..fafb206d39 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -222,7 +222,7 @@ func openWebAppImpl( } else { source = url.isEmpty ? .generic : .simple } - let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: nil, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in presentImpl?(c, a) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 939418a244..2cda37f8b7 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1423,7 +1423,11 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_check_location": self.checkLocation() case "web_app_open_location_settings": - break + if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { + self.webView?.lastTouchTimestamp = nil + + self.openLocationSettings() + } case "web_app_send_prepared_message": if let json = json, let id = json["id"] as? String { self.sendPreparedMessage(id: id) @@ -2409,10 +2413,9 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let self, let controller = self.controller else { return } + let context = self.context + let botId = controller.botId if result { - let context = self.context - let botId = controller.botId - if !context.isPremium { var replaceImpl: ((ViewController) -> Void)? let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .emojiStatus, forceDark: false, action: { @@ -2431,7 +2434,7 @@ public final class WebAppController: ViewController, AttachmentContainable { |> deliverOnMainQueue).startStandalone(completed: { [weak self] in self?.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"allowed\"}") }) - + //TODO:localize if let botPeer { let resultController = UndoOverlayController( @@ -2451,6 +2454,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } else { self.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}") } + + let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in + return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) + }.startStandalone() } ) alertController.dismissed = { [weak self] byOutsideTap in @@ -2550,6 +2557,21 @@ public final class WebAppController: ViewController, AttachmentContainable { }) } + fileprivate func openLocationSettings() { + guard let controller = self.controller else { + return + } + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let controller = self.controller, let peer else { + return + } + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + controller.parentController()?.push(infoController) + } + }) + } + fileprivate func checkLocation() { guard let controller = self.controller else { return @@ -2671,7 +2693,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string) } let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in - return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: result)) + return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: result), emojiStatus: current?.emojiStatus) }.start() } ) @@ -2960,6 +2982,17 @@ public final class WebAppController: ViewController, AttachmentContainable { self?.controllerNode.webView?.reload() }))) + + //TODO:localize + if let _ = self?.appName { + items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + self?.controllerNode.addToHomeScreen() + }))) + } items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_TermsOfUse, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) @@ -3002,18 +3035,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: self.presentationData.strings.WebApp_PrivacyPolicy_URL, forceExternal: false, presentationData: self.presentationData, navigationController: self.getNavigationController(), dismissInput: {}) } }))) - - #if DEBUG - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] c, _ in - c?.dismiss(completion: nil) - - self?.controllerNode.addToHomeScreen() - }))) - #endif - + if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(source) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) diff --git a/submodules/WebUI/Sources/WebAppPermissions.swift b/submodules/WebUI/Sources/WebAppPermissions.swift index 2a53edab6e..0af0fe6907 100644 --- a/submodules/WebUI/Sources/WebAppPermissions.swift +++ b/submodules/WebUI/Sources/WebAppPermissions.swift @@ -8,6 +8,7 @@ import TelegramUIPreferences public struct WebAppPermissionsState: Codable { enum CodingKeys: String, CodingKey { case location + case emojiStatus } public struct Location: Codable { @@ -41,25 +42,56 @@ public struct WebAppPermissionsState: Codable { try container.encode(self.isAllowed, forKey: .isAllowed) } } + + public struct EmojiStatus: Codable { + enum CodingKeys: String, CodingKey { + case isRequested + } + + public let isRequested: Bool + + public init( + isRequested: Bool + ) { + self.isRequested = isRequested + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.isRequested = try container.decode(Bool.self, forKey: .isRequested) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.isRequested, forKey: .isRequested) + } + } public let location: Location? + public let emojiStatus: EmojiStatus? public init( - location: Location? + location: Location?, + emojiStatus: EmojiStatus? ) { self.location = location + self.emojiStatus = emojiStatus } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.location = try container.decode(WebAppPermissionsState.Location.self, forKey: .location) + self.location = try container.decodeIfPresent(WebAppPermissionsState.Location.self, forKey: .location) + self.emojiStatus = try container.decodeIfPresent(WebAppPermissionsState.EmojiStatus.self, forKey: .emojiStatus) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(self.location, forKey: .location) + try container.encodeIfPresent(self.emojiStatus, forKey: .emojiStatus) } }