Various Improvements

This commit is contained in:
Ilya Laktyushin 2022-03-23 00:54:05 +04:00
parent 72c29a5c54
commit 91d94884e8
56 changed files with 856 additions and 303 deletions

Binary file not shown.

View 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";

View File

@ -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)

View File

@ -27,6 +27,8 @@ swift_library(
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
"//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI",
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
"//submodules/ContextUI:ContextUI",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
],
visibility = [
"//visibility:public",

View File

@ -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) }

View File

@ -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()

View File

@ -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))

View File

@ -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()

View File

@ -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)

View File

@ -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?>()

View File

@ -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 {

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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))

View File

@ -9,7 +9,7 @@
"scale" : "2x"
},
{
"filename" : "Snowflake.png",
"filename" : "Tmp.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -1,9 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"provides-namespace" : true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -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
}
}

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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
}
}

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Size=10px-2.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -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 {

View File

@ -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 = {

View File

@ -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,

View File

@ -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
}

View File

@ -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()
}
}
}

View File

@ -532,7 +532,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openAddToChat: {
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -159,7 +159,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openAddToChat: {
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -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)

View File

@ -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 {

View File

@ -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))

View File

@ -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))

View File

@ -1325,7 +1325,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openAddToChat: {
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -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):

View File

@ -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

View File

@ -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",

View 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
}

View 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)
}
}

View File

@ -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)
}
}

View File

@ -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)))
}
}
}