diff --git a/Telegram/Telegram-iOS/Resources/ChangePhoneNumber.tgs b/Telegram/Telegram-iOS/Resources/ChangePhoneNumber.tgs new file mode 100644 index 0000000000..fc113e1da5 Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/ChangePhoneNumber.tgs differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index cacc0bc915..c9394f3a0b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7400,3 +7400,10 @@ Sorry for the inconvenience."; "PeerInfo.ButtonStop" = "Stop"; "Localization.ShowTranslateInfoExtended" = "Show 'Translate' button in the message context menu.\n\nGoogle may have access to the messages you translate."; + +"WebApp.OpenBot" = "Open Bot"; +"WebApp.ReloadPage" = "Reload Page"; +"WebApp.RemoveBot" = "Remove Bot"; + +"WebApp.AddToAttachmentText" = "%@ asks your permission to be added as an option to your attachments menu so you access it from any chat."; +"WebApp.AddToAttachmentAdd" = "Add"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3ac3ef9fca..8075aa471b 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -168,13 +168,73 @@ public enum ResolvedUrlSettingsSection { case devices } +public struct ResolvedBotAdminRights: OptionSet { + public var rawValue: UInt32 + + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let changeInfo = ResolvedBotAdminRights(rawValue: 1) + public static let postMessages = ResolvedBotAdminRights(rawValue: 2) + public static let editMessages = ResolvedBotAdminRights(rawValue: 4) + public static let deleteMessages = ResolvedBotAdminRights(rawValue: 16) + public static let restrictMembers = ResolvedBotAdminRights(rawValue: 32) + public static let inviteUsers = ResolvedBotAdminRights(rawValue: 64) + public static let pinMessages = ResolvedBotAdminRights(rawValue: 128) + public static let promoteMembers = ResolvedBotAdminRights(rawValue: 256) + public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512) + public static let manageChat = ResolvedBotAdminRights(rawValue: 1024) + public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 2048) + + public var chatAdminRights: TelegramChatAdminRightsFlags { + var flags = TelegramChatAdminRightsFlags() + + if self.contains(ResolvedBotAdminRights.changeInfo) { + flags.insert(.canChangeInfo) + } + if self.contains(ResolvedBotAdminRights.postMessages) { + flags.insert(.canPostMessages) + } + if self.contains(ResolvedBotAdminRights.editMessages) { + flags.insert(.canEditMessages) + } + if self.contains(ResolvedBotAdminRights.deleteMessages) { + flags.insert(.canDeleteMessages) + } + if self.contains(ResolvedBotAdminRights.restrictMembers) { + flags.insert(.canBanUsers) + } + if self.contains(ResolvedBotAdminRights.inviteUsers) { + flags.insert(.canInviteUsers) + } + if self.contains(ResolvedBotAdminRights.pinMessages) { + flags.insert(.canPinMessages) + } + if self.contains(ResolvedBotAdminRights.promoteMembers) { + flags.insert(.canAddAdmins) + } + if self.contains(ResolvedBotAdminRights.manageVideoChats) { + flags.insert(.canManageCalls) + } + if self.contains(ResolvedBotAdminRights.canBeAnonymous) { + flags.insert(.canBeAnonymous) + } + return flags + } +} + public enum ResolvedUrl { case externalUrl(String) case urlAuth(String) case peer(PeerId?, ChatControllerInteractionNavigateToPeer) case inaccessiblePeer case botStart(peerId: PeerId, payload: String) - case groupBotStart(peerId: PeerId, payload: String) + case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?) case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?) case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId) case stickerPack(name: String) diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 38ab706a33..8fd12635d1 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -27,6 +27,8 @@ swift_library( "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", "//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI", "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI", + "//submodules/ContextUI:ContextUI", + "//submodules/ManagedAnimationNode:ManagedAnimationNode", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 28937571d7..3437cd0aa0 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -90,7 +90,7 @@ private final class AttachButtonComponent: CombinedComponent { imageName = "Chat/Attach Menu/Poll" case let .app(appName): name = appName - imageName = nil + imageName = "Chat List/Tabs/IconSettings" } let image = imageName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift b/submodules/AttachmentUI/Sources/MoreButtonNode.swift similarity index 90% rename from submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift rename to submodules/AttachmentUI/Sources/MoreButtonNode.swift index f73f26c503..3317af62bf 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift +++ b/submodules/AttachmentUI/Sources/MoreButtonNode.swift @@ -6,15 +6,15 @@ import TelegramPresentationData import ManagedAnimationNode import ContextUI -final class MediaPickerMoreButtonNode: ASDisplayNode { - class MoreIconNode: ManagedAnimationNode { - enum State: Equatable { +public final class MoreButtonNode: ASDisplayNode { + public class MoreIconNode: ManagedAnimationNode { + public enum State: Equatable { case more case search } private let duration: Double = 0.21 - var iconState: State = .search + public var iconState: State = .search init() { super.init(size: CGSize(width: 30.0, height: 30.0)) @@ -28,7 +28,7 @@ final class MediaPickerMoreButtonNode: ASDisplayNode { } } - func enqueueState(_ state: State, animated: Bool) { + public func enqueueState(_ state: State, animated: Bool) { guard self.iconState != state else { return } @@ -67,20 +67,20 @@ final class MediaPickerMoreButtonNode: ASDisplayNode { } } - var action: ((ASDisplayNode, ContextGesture?) -> Void)? + public var action: ((ASDisplayNode, ContextGesture?) -> Void)? private let containerNode: ContextControllerSourceNode - let contextSourceNode: ContextReferenceContentNode + public let contextSourceNode: ContextReferenceContentNode private let buttonNode: HighlightableButtonNode - let iconNode: MoreIconNode + public let iconNode: MoreIconNode - var theme: PresentationTheme { + public var theme: PresentationTheme { didSet { self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor } } - init(theme: PresentationTheme) { + public init(theme: PresentationTheme) { self.theme = theme self.contextSourceNode = ContextReferenceContentNode() diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 850a623d0c..eca2079bda 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -115,7 +115,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let collection: PHAssetCollection? private let titleView: MediaPickerTitleView - private let moreButtonNode: MediaPickerMoreButtonNode + private let moreButtonNode: MoreButtonNode public weak var webSearchController: WebSearchController? @@ -1078,7 +1078,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) self.titleView.title = collection?.localizedTitle ?? presentationData.strings.Attachment_Gallery - self.moreButtonNode = MediaPickerMoreButtonNode(theme: self.presentationData.theme) + self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index a9a07c0095..c8ef27cb87 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -585,12 +585,6 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s ] } - if invite { - maskRightsFlags.remove(.canManageCalls) - maskRightsFlags.remove(.canBeAnonymous) - maskRightsFlags.remove(.canAddAdmins) - } - if isCreator { if isGroup { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) @@ -747,24 +741,18 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s let isGroup = true let isChannel = false - var maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific + let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific let rightsOrder: [TelegramChatAdminRightsFlags] = [ - .canChangeInfo, - .canDeleteMessages, - .canBanUsers, - .canInviteUsers, - .canPinMessages, - .canManageCalls, - .canBeAnonymous, - .canAddAdmins - ] - - if invite { - maskRightsFlags.remove(.canManageCalls) - maskRightsFlags.remove(.canBeAnonymous) - maskRightsFlags.remove(.canAddAdmins) - } - + .canChangeInfo, + .canDeleteMessages, + .canBanUsers, + .canInviteUsers, + .canPinMessages, + .canManageCalls, + .canBeAnonymous, + .canAddAdmins + ] + let accountUserRightsFlags: TelegramChatAdminRightsFlags = maskRightsFlags let currentRightsFlags: TelegramChatAdminRightsFlags @@ -812,13 +800,19 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s return entries } -public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, invite: Bool = false, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, transferedOwnership: @escaping (PeerId) -> Void) -> ViewController { +public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, invite: Bool = false, initialAdminRights: TelegramChatAdminRightsFlags? = nil, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, transferedOwnership: @escaping (PeerId) -> Void) -> ViewController { let statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelAdminControllerState()) let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } + if let initialAdminRights = initialAdminRights { + updateState { + return $0.withUpdatedUpdatedFlags(initialAdminRights) + } + } + let actionsDisposable = DisposableSet() let updateRightsDisposable = MetaDisposable() diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index 8acb504124..1c20235e74 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -14,7 +14,7 @@ import PhoneNumberFormat import CoreTelephony import MessageUI -final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate { +public final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate { private var controllerNode: ChangePhoneNumberControllerNode { return self.displayNode as! ChangePhoneNumberControllerNode } @@ -41,7 +41,7 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro private var presentationData: PresentationData - init(context: AccountContext) { + public init(context: AccountContext) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -98,19 +98,19 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) } - override func viewWillAppear(_ animated: Bool) { + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.controllerNode.activateInput() } - override func viewDidAppear(_ animated: Bool) { + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.controllerNode.activateInput() } - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition) diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index 13c9e68b1e..54f275b104 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -152,7 +152,10 @@ public func logoutOptionsController(context: AccountContext, navigationControlle pushControllerImpl?(storageUsageController(context: context)) dismissImpl?() }, changePhoneNumber: { - pushControllerImpl?(ChangePhoneNumberIntroController(context: context, phoneNumber: phoneNumber)) + let introController = PrivacyIntroController(context: context, mode: .changePhoneNumber(phoneNumber), proceedAction: { + replaceTopControllerImpl?(ChangePhoneNumberController(context: context)) + }) + pushControllerImpl?(introController) dismissImpl?() }, contactSupport: { [weak navigationController] in let supportPeer = Promise() diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift index 0449e6daa1..4fafef3f8e 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift @@ -8,15 +8,19 @@ import TelegramCore import TelegramPresentationData import AccountContext import AppBundle +import PhoneNumberFormat -enum PrivacyIntroControllerMode { +public enum PrivacyIntroControllerMode { case passcode case twoStepVerification + case changePhoneNumber(String) var animationName: String? { switch self { case .passcode: return "Passcode" + case .changePhoneNumber: + return "ChangePhoneNumber" case .twoStepVerification: return nil } @@ -24,10 +28,8 @@ enum PrivacyIntroControllerMode { func icon(theme: PresentationTheme) -> UIImage? { switch self { - case .passcode: + case .passcode, .changePhoneNumber, .twoStepVerification: return generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeIntroIcon"), color: theme.list.freeTextColor) - case .twoStepVerification: - return generateTintedImage(image: UIImage(bundleImageName: "Settings/PasswordIntroIcon"), color: theme.list.freeTextColor) } } @@ -37,6 +39,8 @@ enum PrivacyIntroControllerMode { return strings.PasscodeSettings_Title case .twoStepVerification: return strings.PrivacySettings_TwoStepAuth + case .changePhoneNumber: + return strings.ChangePhoneNumberNumber_Title } } @@ -46,6 +50,8 @@ enum PrivacyIntroControllerMode { return strings.PasscodeSettings_Title case .twoStepVerification: return strings.TwoStepAuth_AdditionalPassword + case let .changePhoneNumber(phoneNumber): + return formatPhoneNumber(phoneNumber) } } @@ -55,15 +61,19 @@ enum PrivacyIntroControllerMode { return strings.PasscodeSettings_HelpTop case .twoStepVerification: return strings.TwoStepAuth_SetPasswordHelp + case .changePhoneNumber: + return strings.PhoneNumberHelp_Help } } func buttonTitle(strings: PresentationStrings) -> String { switch self { - case .passcode: - return strings.PasscodeSettings_TurnPasscodeOn - case .twoStepVerification: - return strings.TwoStepAuth_SetPassword + case .passcode: + return strings.PasscodeSettings_TurnPasscodeOn + case .twoStepVerification: + return strings.TwoStepAuth_SetPassword + case .changePhoneNumber: + return strings.PhoneNumberHelp_ChangeNumber } } @@ -71,7 +81,7 @@ enum PrivacyIntroControllerMode { switch self { case .passcode: return strings.PasscodeSettings_HelpBottom - case .twoStepVerification: + case .twoStepVerification, .changePhoneNumber: return "" } } @@ -87,7 +97,7 @@ public final class PrivacyIntroControllerPresentationArguments { } } -final class PrivacyIntroController: ViewController { +public final class PrivacyIntroController: ViewController { private let context: AccountContext private let mode: PrivacyIntroControllerMode private let arguments: PrivacyIntroControllerPresentationArguments @@ -102,7 +112,7 @@ final class PrivacyIntroController: ViewController { private var isDismissed: Bool = false - init(context: AccountContext, mode: PrivacyIntroControllerMode, arguments: PrivacyIntroControllerPresentationArguments = PrivacyIntroControllerPresentationArguments(), proceedAction: @escaping () -> Void) { + public init(context: AccountContext, mode: PrivacyIntroControllerMode, arguments: PrivacyIntroControllerPresentationArguments = PrivacyIntroControllerPresentationArguments(), proceedAction: @escaping () -> Void) { self.context = context self.mode = mode self.arguments = arguments @@ -116,6 +126,8 @@ final class PrivacyIntroController: ViewController { self.title = self.mode.controllerTitle(strings: self.presentationData.strings) + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + if arguments.animateIn { self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) } @@ -161,7 +173,7 @@ final class PrivacyIntroController: ViewController { self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) } - override func viewDidAppear(_ animated: Bool) { + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if self.arguments.animateIn { self.controllerNode.animateIn(slide: true) @@ -170,7 +182,7 @@ final class PrivacyIntroController: ViewController { } } - override func dismiss(completion: (() -> Void)? = nil) { + override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true if self.arguments.animateIn { diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 1027d7354d..f401eca9f2 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -193,7 +193,9 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? "" } |> deliverOnMainQueue).start(next: { phoneNumber in - present(.push, ChangePhoneNumberIntroController(context: context, phoneNumber: formatPhoneNumber(phoneNumber))) + present(.push, PrivacyIntroController(context: context, mode: .changePhoneNumber(phoneNumber), proceedAction: { + present(.push, ChangePhoneNumberController(context: context)) + })) }) })) items.append(SettingsSearchableItem(id: .profile(3), title: strings.Settings_Username, alternate: synonyms(strings.SettingsSearch_Synonyms_EditProfile_Username), icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift index 7e558a81cf..815f8139cc 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift @@ -12,7 +12,6 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { case urlAuth(url: String, buttonId: Int32) case setupPoll(isQuiz: Bool?) case openUserProfile(peerId: PeerId) - case addToChat public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { @@ -38,8 +37,6 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 }) case 10: self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0))) - case 11: - self = .addToChat default: self = .text } @@ -82,8 +79,6 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable { case let .openUserProfile(peerId): encoder.encodeInt32(10, forKey: "v") encoder.encodeInt64(peerId.toInt64(), forKey: "peerId") - case .addToChat: - encoder.encodeInt32(11, forKey: "v") } } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 651e2f8c55..e101ca8a67 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -541,6 +541,7 @@ public final class PrincipalThemeAdditionalGraphics { public let chatBubbleActionButtonIncomingPaymentIconImage: UIImage public let chatBubbleActionButtonIncomingProfileIconImage: UIImage public let chatBubbleActionButtonIncomingAddToChatIconImage: UIImage + public let chatBubbleActionButtonIncomingWebAppIconImage: UIImage public let chatBubbleActionButtonOutgoingMessageIconImage: UIImage public let chatBubbleActionButtonOutgoingLinkIconImage: UIImage @@ -550,6 +551,7 @@ public final class PrincipalThemeAdditionalGraphics { public let chatBubbleActionButtonOutgoingPaymentIconImage: UIImage public let chatBubbleActionButtonOutgoingProfileIconImage: UIImage public let chatBubbleActionButtonOutgoingAddToChatIconImage: UIImage + public let chatBubbleActionButtonOutgoingWebAppIconImage: UIImage public let chatEmptyItemLockIcon: UIImage public let emptyChatListCheckIcon: UIImage @@ -594,6 +596,7 @@ public final class PrincipalThemeAdditionalGraphics { self.chatBubbleActionButtonIncomingPaymentIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPayment"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonIncomingProfileIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotProfile"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonIncomingAddToChatIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotAddToChat"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))! + self.chatBubbleActionButtonIncomingWebAppIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotWebApp"), color: bubbleVariableColor(variableColor: theme.message.incoming.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonOutgoingMessageIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonOutgoingLinkIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonOutgoingShareIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotShare"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! @@ -602,6 +605,7 @@ public final class PrincipalThemeAdditionalGraphics { self.chatBubbleActionButtonOutgoingPaymentIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotPayment"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonOutgoingProfileIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotProfile"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! self.chatBubbleActionButtonOutgoingAddToChatIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotAddToChat"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! + self.chatBubbleActionButtonOutgoingWebAppIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotWebApp"), color: bubbleVariableColor(variableColor: theme.message.outgoing.actionButtonsTextColor, wallpaper: wallpaper))! self.chatEmptyItemLockIcon = generateImage(CGSize(width: 9.0, height: 13.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json similarity index 88% rename from submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json index 204c66f291..96acb63cc3 100644 --- a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json @@ -9,7 +9,7 @@ "scale" : "2x" }, { - "filename" : "Snowflake.png", + "filename" : "Tmp.png", "idiom" : "universal", "scale" : "3x" } diff --git a/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png new file mode 100644 index 0000000000..a74a40092b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Bot Payments/Contents.json b/submodules/TelegramUI/Images.xcassets/Bot Payments/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Bot Payments/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Bot Payments/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/Contents.json deleted file mode 100644 index 38f0c81fc2..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/Contents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "provides-namespace" : true - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@2x.png deleted file mode 100644 index 3d12d7e27b..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@3x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@3x.png deleted file mode 100644 index 06f12c914e..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Calls@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Contents.json deleted file mode 100644 index 802b6afe81..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconCalls.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Calls@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Calls@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Contents.json deleted file mode 100644 index b0d2ce266b..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Messages@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Messages@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@2x.png deleted file mode 100644 index ffa49c3ae1..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@3x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@3x.png deleted file mode 100644 index 324a417e72..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconChats.imageset/Messages@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@2x.png deleted file mode 100644 index 115d8526c5..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@3x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@3x.png deleted file mode 100644 index aaddd096eb..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contacts@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contents.json deleted file mode 100644 index 1cc3b663ca..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconContacts.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Contacts@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Contacts@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Contents.json deleted file mode 100644 index 2ea1fc8abd..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Settings@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Settings@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@2x.png deleted file mode 100644 index c00ec0876d..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@3x.png b/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@3x.png deleted file mode 100644 index 27cad3a239..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Tabs/Holiday/IconSettings.imageset/Settings@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/Contents.json deleted file mode 100644 index 95fe8c9b15..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "tip@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "tip@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@2x.png b/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@2x.png deleted file mode 100644 index 401374b9f5..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@3x.png b/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@3x.png deleted file mode 100644 index c8962f67d0..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/UndoInfoIcon.imageset/tip@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Contents.json new file mode 100644 index 0000000000..9a37ae1eac --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Size=10px-2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Size=10px-2.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Size=10px-2.pdf new file mode 100644 index 0000000000..cacabf54b7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Size=10px-2.pdf @@ -0,0 +1,85 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.000000 1.000000 cm +0.000000 0.000000 0.000000 scn +2.000000 8.000000 m +0.895431 8.000000 0.000000 7.104569 0.000000 6.000000 c +0.000000 2.000000 l +0.000000 0.895431 0.895431 0.000000 2.000000 0.000000 c +6.000000 0.000000 l +7.104569 0.000000 8.000000 0.895431 8.000000 2.000000 c +8.000000 6.000000 l +8.000000 7.104569 7.104569 8.000000 6.000000 8.000000 c +2.000000 8.000000 l +h +2.000002 5.669998 m +1.629971 5.669998 1.330002 5.370029 1.330002 4.999998 c +1.330002 2.009998 l +1.330002 1.639967 1.629971 1.339998 2.000001 1.339998 c +5.990002 1.339998 l +6.360033 1.339998 6.660002 1.639967 6.660002 2.009998 c +6.660002 4.999998 l +6.660002 5.370029 6.360033 5.669998 5.990002 5.669998 c +2.000002 5.669998 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 778 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 10.000000 10.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000868 00000 n +0000000890 00000 n +0000001063 00000 n +0000001137 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1196 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png b/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png deleted file mode 100644 index f781e57161..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png and /dev/null differ diff --git a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift index 54b4aa55dd..ee3274981a 100644 --- a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift @@ -215,8 +215,6 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { self.controllerInteraction.openPollCreation(isQuiz) case let .openUserProfile(peerId): self.controllerInteraction.openPeer(peerId, .info, nil, nil) - case .addToChat: - self.controllerInteraction.openAddToChat() } if dismissIfOnce { if let message = self.message { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bb5e623c08..08e352840f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -75,6 +75,7 @@ import ChatPresentationInterfaceState import Pasteboard import ChatSendMessageActionUI import ChatTextLinkEditUI +import WebUI #if DEBUG import os.signpost @@ -3298,23 +3299,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil) - }, openAddToChat: { [weak self] in - guard let strongSelf = self else { - return - } - guard let peerId = strongSelf.presentationInterfaceState.chatLocation.peerId else { - return - } - strongSelf.context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: ""), context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.effectiveNavigationController, openPeer: { id, navigation in - }, sendFile: nil, - sendSticker: nil, - requestMessageActionUrlAuth: nil, - joinVoiceChat: nil, - present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - }, dismissInput: { [weak self] in - self?.view.endEditing(true) - }, contentContext: nil) }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -10435,6 +10419,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canSendPolls { availableTabs.insert(.poll, at: availableTabs.count - 1) } +// availableTabs.insert(.app("Web App"), at: 1) let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText @@ -10700,7 +10685,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G completion(controller, nil) strongSelf.controllerNavigationDisposable.set(nil) case .app: - return + let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, url: "", message: nil) + completion(controller, nil) + strongSelf.controllerNavigationDisposable.set(nil) } } let present = { diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index fc16e56473..acff7b34b1 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -131,7 +131,6 @@ public final class ChatControllerInteraction { let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void let openJoinLink: (String) -> Void - let openAddToChat: () -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -231,7 +230,6 @@ public final class ChatControllerInteraction { commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void, openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, openJoinLink: @escaping (String) -> Void, - openAddToChat: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, @@ -317,7 +315,6 @@ public final class ChatControllerInteraction { self.commitEmojiInteraction = commitEmojiInteraction self.openLargeEmojiInfo = openLargeEmojiInfo self.openJoinLink = openJoinLink - self.openAddToChat = openAddToChat self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures @@ -377,7 +374,6 @@ public final class ChatControllerInteraction { }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift index fb16be1edb..57ec1c7151 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift @@ -94,7 +94,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { switch button.action { case .text: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage - case .url, .urlAuth: + case let .url(value): + if value.lowercased().contains("?startgroup=") { + iconImage = incoming ? graphics.chatBubbleActionButtonIncomingAddToChatIconImage : graphics.chatBubbleActionButtonOutgoingAddToChatIconImage + } else { + iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLinkIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage + } + case .urlAuth: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLinkIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage case .requestPhone: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPhoneIconImage : graphics.chatBubbleActionButtonOutgoingPhoneIconImage @@ -106,8 +112,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage case .openUserProfile: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage - case .addToChat: - iconImage = incoming ? graphics.chatBubbleActionButtonIncomingAddToChatIconImage : graphics.chatBubbleActionButtonOutgoingAddToChatIconImage + case .openWebApp: + iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage default: iconImage = nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index b67c735cb4..c111678fb1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -854,8 +854,6 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol break case let .openUserProfile(peerId): item.controllerInteraction.openPeer(peerId, .info, nil, nil) - case .addToChat: - item.controllerInteraction.openAddToChat() } } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 41fd0f9401..105475eb41 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -532,7 +532,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 9bea55cb0d..66cc0c9175 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -159,7 +159,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 138305c63b..c605239805 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -67,7 +67,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) case let .botStart(peerId, payload): openPeer(peerId, .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive))) - case let .groupBotStart(botPeerId, payload): + case let .groupBotStart(botPeerId, payload, adminRights): let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title)) controller.peerSelected = { [weak controller] peer in let peerId = peer.id @@ -132,7 +132,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur if let peer = peer as? TelegramChannel { if peer.flags.contains(.isCreator) || peer.adminRights != nil { - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, updated: { _ in + let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: adminRights?.chatAdminRights, updated: { _ in controller?.dismiss() }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) navigationController?.pushViewController(controller) @@ -143,7 +143,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur if case .member = peer.role { addMemberImpl() } else { - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, updated: { _ in + let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: adminRights?.chatAdminRights, updated: { _ in controller?.dismiss() }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) navigationController?.pushViewController(controller) diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 31096d46e3..c75f110003 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -610,6 +610,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur var domain: String? var start: String? var startGroup: String? + var admin: String? var game: String? var post: String? var voiceChat: String? @@ -624,6 +625,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur start = value } else if queryItem.name == "startgroup" { startGroup = value + } else if queryItem.name == "admin" { + admin = value } else if queryItem.name == "game" { game = value } else if queryItem.name == "post" { @@ -648,6 +651,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur result += "?start=\(start)" } else if let startGroup = startGroup { result += "?startgroup=\(startGroup)" + if let admin = admin { + result += "&admin=\(admin)" + } } else if let game = game { result += "?game=\(game)" } else if let voiceChat = voiceChat { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 31739fd2ae..9026c3295d 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -151,7 +151,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil)) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d4c22dd610..768b7d40e0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2265,7 +2265,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, @@ -4787,7 +4786,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let controller = self.controller else { return } - self.context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: ""), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, openPeer: { id, navigation in + self.context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: "", adminRights: nil), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, openPeer: { id, navigation in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, @@ -5648,7 +5647,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.openTips() case .phoneNumber: if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { - self.controller?.push(ChangePhoneNumberIntroController(context: self.context, phoneNumber: phoneNumber)) + let introController = PrivacyIntroController(context: self.context, mode: .changePhoneNumber(phoneNumber), proceedAction: { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { + navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true) + } + }) + self.controller?.push(introController) } case .username: self.controller?.push(usernameSetupController(context: self.context)) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index cc74cf4f79..8c00a2f4b8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1325,7 +1325,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, commitEmojiInteraction: { _, _, _, _ in }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in - }, openAddToChat: { }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index a423819fa0..26328af18c 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -19,9 +19,53 @@ private let baseTelegraPhPaths = [ "telegram.org/tour/" ] +extension ResolvedBotAdminRights { + init?(_ string: String) { + var rawValue: UInt32 = 0 + + let components = string.lowercased().components(separatedBy: "+") + if components.contains("change_info") { + rawValue |= ResolvedBotAdminRights.changeInfo.rawValue + } + if components.contains("post_messages") { + rawValue |= ResolvedBotAdminRights.postMessages.rawValue + } + if components.contains("delete_messages") { + rawValue |= ResolvedBotAdminRights.deleteMessages.rawValue + } + if components.contains("restrict_members") { + rawValue |= ResolvedBotAdminRights.restrictMembers.rawValue + } + if components.contains("invite_users") { + rawValue |= ResolvedBotAdminRights.inviteUsers.rawValue + } + if components.contains("pin_messages") { + rawValue |= ResolvedBotAdminRights.pinMessages.rawValue + } + if components.contains("promote_members") { + rawValue |= ResolvedBotAdminRights.promoteMembers.rawValue + } + if components.contains("manage_video_chats") { + rawValue |= ResolvedBotAdminRights.manageVideoChats.rawValue + } + if components.contains("manage_chat") { + rawValue |= ResolvedBotAdminRights.manageChat.rawValue + } + if components.contains("anonymous") { + rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue + } + + if rawValue != 0 { + self.init(rawValue: rawValue) + } else { + return nil + } + } +} + public enum ParsedInternalPeerUrlParameter { case botStart(String) - case groupBotStart(String) + case groupBotStart(String, ResolvedBotAdminRights?) case channelMessage(Int32, Double?) case replyThread(Int32, Int32) case voiceChat(String?) @@ -141,7 +185,14 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if queryItem.name == "start" { return .peerName(peerName, .botStart(value)) } else if queryItem.name == "startgroup" { - return .peerName(peerName, .groupBotStart(value)) + var botAdminRights: ResolvedBotAdminRights? + for queryItem in queryItems { + if queryItem.name == "admin", let value = queryItem.value { + botAdminRights = ResolvedBotAdminRights(value) + break + } + } + return .peerName(peerName, .groupBotStart(value, botAdminRights)) } else if queryItem.name == "game" { return nil } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { @@ -378,8 +429,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) switch parameter { case let .botStart(payload): return .single(.botStart(peerId: peer.id, payload: payload)) - case let .groupBotStart(payload): - return .single(.groupBotStart(peerId: peer.id, payload: payload)) + case let .groupBotStart(payload, adminRights): + return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights)) case let .channelMessage(id, timecode): return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) case let .replyThread(id, replyId): diff --git a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift index 30e1c542f4..4321d495b8 100644 --- a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift +++ b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift @@ -36,7 +36,7 @@ public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concea var parsedUrlValue: URL? if url.hasPrefix("tel:") { return (url, false) - } else if let parsed = URL(string: url) { + } else if url.lowercased().hasPrefix("http://") || url.lowercased().hasPrefix("https://"), let parsed = URL(string: url) { parsedUrlValue = parsed } else if let parsed = URL(string: "https://" + url) { parsedUrlValue = parsed diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index bbd1827908..33468d677f 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -10,8 +10,14 @@ swift_library( "-warnings-as-errors", ], deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/AttachmentUI:AttachmentUI", + "//submodules/CounterContollerTitleView:CounterContollerTitleView", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppAlertContentNode.swift b/submodules/WebUI/Sources/WebAppAlertContentNode.swift new file mode 100644 index 0000000000..35752610e4 --- /dev/null +++ b/submodules/WebUI/Sources/WebAppAlertContentNode.swift @@ -0,0 +1,225 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle + +private final class WebAppAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + + private let textNode: ASTextNode + private let iconNode: ASImageNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var validLayout: CGSize? + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) { + self.strings = strings + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 0 + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.textNode) + self.addSubnode(self.iconNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.updateTheme(theme) + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText("Web App").string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Bot Payments/BotLogo"), color: theme.accentColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width , 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + + var iconSize = CGSize() + var iconFrame = CGRect() + if let icon = self.iconNode.image { + iconSize = icon.size + iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: origin.y), size: iconSize) + origin.y += iconSize.height + 16.0 + } + + let textSize = self.textNode.measure(size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize) + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + var contentWidth = max(textSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + let resultSize = CGSize(width: resultWidth, height: iconSize.height + textSize.height + actionsHeight + 17.0 + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + iconFrame.origin.x = floorToScreenPixels((resultSize.width - iconFrame.width) / 2.0) + transition.updateFrame(node: self.iconNode, frame: iconFrame) + + textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0) + transition.updateFrame(node: self.textNode, frame: textFrame) + + return resultSize + } +} + +func addWebAppToAttachmentController(sharedContext: SharedAccountContext) -> AlertController { + let presentationData = sharedContext.currentPresentationData.with { $0 } + let theme = presentationData.theme + let strings = presentationData.strings + + var dismissImpl: ((Bool) -> Void)? + var contentNode: WebAppAlertContentNode? + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: { + dismissImpl?(true) + + })] + + contentNode = WebAppAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, actions: actions) + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift new file mode 100644 index 0000000000..961a506ba1 --- /dev/null +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -0,0 +1,293 @@ +import Foundation +import UIKit +import WebKit +import Display +import AsyncDisplayKit +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import AttachmentUI +import CounterContollerTitleView +import ContextUI + +private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + +public final class WebAppController: ViewController, AttachmentContainable { + public var requestAttachmentMenuExpansion: () -> Void = { } + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var cancelPanGesture: () -> Void = { } + + private class Node: ViewControllerTracingNode { + private var webView: WKWebView? + + private let context: AccountContext + var presentationData: PresentationData + private let present: (ViewController, Any?) -> Void + private let message: EngineMessage? + + init(context: AccountContext, presentationData: PresentationData, url: String, present: @escaping (ViewController, Any?) -> Void, message: EngineMessage?) { + self.context = context + self.presentationData = presentationData + self.present = present + self.message = message + + super.init() + + self.backgroundColor = .white + + let js = "var TelegramWebviewProxyProto = function() {}; " + + "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + + "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + + "}; " + + "var TelegramWebviewProxy = new TelegramWebviewProxyProto();" + + let configuration = WKWebViewConfiguration() + let userController = WKUserContentController() + + let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) + userController.addUserScript(userScript) + + userController.add(WeakGameScriptMessageHandler { [weak self] message in + if let strongSelf = self { + strongSelf.handleScriptMessage(message) + } + }, name: "performAction") + + configuration.userContentController = userController + + configuration.allowsInlineMediaPlayback = true + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + configuration.mediaTypesRequiringUserActionForPlayback = [] + } else if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { + configuration.requiresUserActionForMediaPlayback = false + } else { + configuration.mediaPlaybackRequiresUserAction = false + } + + let webView = WKWebView(frame: CGRect(), configuration: configuration) + if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { + webView.allowsLinkPreview = false + } + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + webView.scrollView.contentInsetAdjustmentBehavior = .never + } + webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in + return point.x > 30.0 + } + + self.view.addSubview(webView) + self.webView = webView + + if let parsedUrl = URL(string: url) { + webView.load(URLRequest(url: parsedUrl)) + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + if let webView = self.webView { + webView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight))) + } + } + + func animateIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func animateOut(completion: (() -> Void)? = nil) { + self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in + completion?() + }) + } + + private func shareData() -> (EnginePeer, String)? { + guard let message = self.message else { + return nil + } + var botPeer: EnginePeer? + var gameName: String? + for media in message.media { + if let game = media as? TelegramMediaGame { + inner: for attribute in message.attributes { + if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId { + botPeer = message.peers[peerId].flatMap(EnginePeer.init) + break inner + } + } + if botPeer == nil { + botPeer = message.author + } + + gameName = game.name + } + } + if let botPeer = botPeer, let gameName = gameName { + return (botPeer, gameName) + } + + return nil + } + + private func handleScriptMessage(_ message: WKScriptMessage) { + guard let body = message.body as? [String: Any] else { + return + } + + guard let eventName = body["eventName"] as? String else { + return + } + + if eventName == "share_game" || eventName == "share_score" { + if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { + if eventName == "share_score" { + + } else { + + } + } + } + } + } + + + private var controllerNode: Node { + return self.displayNode as! Node + } + + private let moreButtonNode: MoreButtonNode + + private let context: AccountContext + private let url: String + private let message: EngineMessage? + + private var presentationData: PresentationData + + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, url: String, message: EngineMessage?) { + self.context = context + self.url = url + self.message = message + + self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + var theme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) + theme = theme.withUpdatedBackgroundColor(self.presentationData.theme.list.plainBackgroundColor) + let navigationBarPresentationData = NavigationBarPresentationData(theme: theme, strings: NavigationBarStrings(back: "", close: "")) + + self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) + self.moreButtonNode.iconNode.enqueueState(.more, animated: false) + + super.init(navigationBarPresentationData: navigationBarPresentationData) + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + + let titleView = CounterContollerTitleView(theme: self.presentationData.theme) + titleView.title = CounterContollerTitle(title: "Web App", counter: self.presentationData.strings.Bot_GenericBotStatus) + self.navigationItem.titleView = titleView + + self.moreButtonNode.action = { [weak self] _, gesture in + if let strongSelf = self { + strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) + } + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + assert(true) + } + + @objc private func cancelPressed() { + self.dismiss() + } + + @objc private func moreButtonPressed() { + self.moreButtonNode.action?(self.moreButtonNode.contextSourceNode, nil) + } + + @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: "Open Bot", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let strongSelf = self else { + return + } + let controller = addWebAppToAttachmentController(sharedContext: strongSelf.context.sharedContext) + strongSelf.present(controller, in: .window(.root)) + }))) + + items.append(.action(ContextMenuActionItem(text: "Reload Page", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + + }))) + + items.append(.action(ContextMenuActionItem(text: "Remove Bot", textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { _, f in + f(.default) + + }))) + + let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(WebAppContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + self.presentInGlobalOverlay(contextController) + } + + override public func loadDisplayNode() { + self.displayNode = Node(context: self.context, presentationData: self.presentationData, url: self.url, present: { [weak self] c, a in + self?.present(c, in: .window(.root), with: a) + }, message: self.message) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + } + + override public var presentationController: UIPresentationController? { + get { + return nil + } set(value) { + } + } +} + +private final class WebAppContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/WebUI/Sources/WebController.swift b/submodules/WebUI/Sources/WebController.swift deleted file mode 100644 index 12f9d4516f..0000000000 --- a/submodules/WebUI/Sources/WebController.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import UIKit -import Display -import SafariServices - -public final class WebController: ViewController { - private let url: URL - - private var controllerNode: WebControllerNode { - return self.displayNode as! WebControllerNode - } - - public init(url: URL) { - self.url = url - - super.init(navigationBarPresentationData: nil) - - self.edgesForExtendedLayout = [] - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func loadDisplayNode() { - self.displayNode = WebControllerNode(url: self.url) - - self.displayNodeDidLoad() - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, transition: transition) - } -} diff --git a/submodules/WebUI/Sources/WebControllerNode.swift b/submodules/WebUI/Sources/WebControllerNode.swift deleted file mode 100644 index 3daf4b7369..0000000000 --- a/submodules/WebUI/Sources/WebControllerNode.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import WebKit - -final class WebControllerNode: ViewControllerTracingNode { - private let webView: WKWebView - - init(url: URL) { - let configuration = WKWebViewConfiguration() - self.webView = WKWebView(frame: CGRect(), configuration: configuration) - if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { - self.webView.allowsLinkPreview = false - } - self.webView.allowsBackForwardNavigationGestures = true - //webView.navigationDelegate = self - - super.init() - - self.view.addSubview(self.webView) - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.webView.scrollView.contentInsetAdjustmentBehavior = .never - } - self.webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) - - self.webView.load(URLRequest(url: url)) - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - transition.animateView { - self.webView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height))) - } - } -}