Various Improvements
BIN
Telegram/Telegram-iOS/Resources/ChangePhoneNumber.tgs
Normal file
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -27,6 +27,8 @@ swift_library(
|
||||
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
|
||||
"//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI",
|
||||
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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) }
|
||||
|
@ -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()
|
@ -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))
|
||||
|
||||
|
@ -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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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()
|
||||
|
@ -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)
|
||||
|
@ -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<PeerId?>()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -9,7 +9,7 @@
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Snowflake.png",
|
||||
"filename" : "Tmp.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.3 KiB |
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.4 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 1.5 KiB |
12
submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Size=10px-2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
85
submodules/TelegramUI/Images.xcassets/Chat/Message/BotWebApp.imageset/Size=10px-2.pdf
vendored
Normal file
@ -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
|
Before Width: | Height: | Size: 5.7 KiB |
@ -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 {
|
||||
|
@ -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 = {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -532,7 +532,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, commitEmojiInteraction: { _, _, _, _ in
|
||||
}, openLargeEmojiInfo: { _, _, _ in
|
||||
}, openJoinLink: { _ in
|
||||
}, openAddToChat: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
@ -159,7 +159,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, commitEmojiInteraction: { _, _, _, _ in
|
||||
}, openLargeEmojiInfo: { _, _, _ in
|
||||
}, openJoinLink: { _ in
|
||||
}, openAddToChat: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -1325,7 +1325,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, commitEmojiInteraction: { _, _, _, _ in
|
||||
}, openLargeEmojiInfo: { _, _, _ in
|
||||
}, openJoinLink: { _ in
|
||||
}, openAddToChat: {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
225
submodules/WebUI/Sources/WebAppAlertContentNode.swift
Normal file
@ -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
|
||||
}
|
293
submodules/WebUI/Sources/WebAppController.swift
Normal file
@ -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<PresentationData, NoError>)? = 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|