Various Improvements
BIN
Telegram/Telegram-iOS/Resources/ChangePhoneNumber.tgs
Normal file
@ -7400,3 +7400,10 @@ Sorry for the inconvenience.";
|
|||||||
"PeerInfo.ButtonStop" = "Stop";
|
"PeerInfo.ButtonStop" = "Stop";
|
||||||
|
|
||||||
"Localization.ShowTranslateInfoExtended" = "Show 'Translate' button in the message context menu.\n\nGoogle may have access to the messages you translate.";
|
"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
|
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 {
|
public enum ResolvedUrl {
|
||||||
case externalUrl(String)
|
case externalUrl(String)
|
||||||
case urlAuth(String)
|
case urlAuth(String)
|
||||||
case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
|
case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
|
||||||
case inaccessiblePeer
|
case inaccessiblePeer
|
||||||
case botStart(peerId: PeerId, payload: String)
|
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 channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?)
|
||||||
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
|
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
|
||||||
case stickerPack(name: String)
|
case stickerPack(name: String)
|
||||||
|
@ -27,6 +27,8 @@ swift_library(
|
|||||||
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
|
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
|
||||||
"//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI",
|
"//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI",
|
||||||
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
|
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
|
||||||
|
"//submodules/ContextUI:ContextUI",
|
||||||
|
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -90,7 +90,7 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
imageName = "Chat/Attach Menu/Poll"
|
imageName = "Chat/Attach Menu/Poll"
|
||||||
case let .app(appName):
|
case let .app(appName):
|
||||||
name = appName
|
name = appName
|
||||||
imageName = nil
|
imageName = "Chat List/Tabs/IconSettings"
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = imageName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) }
|
let image = imageName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) }
|
||||||
|
@ -6,15 +6,15 @@ import TelegramPresentationData
|
|||||||
import ManagedAnimationNode
|
import ManagedAnimationNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
|
||||||
final class MediaPickerMoreButtonNode: ASDisplayNode {
|
public final class MoreButtonNode: ASDisplayNode {
|
||||||
class MoreIconNode: ManagedAnimationNode {
|
public class MoreIconNode: ManagedAnimationNode {
|
||||||
enum State: Equatable {
|
public enum State: Equatable {
|
||||||
case more
|
case more
|
||||||
case search
|
case search
|
||||||
}
|
}
|
||||||
|
|
||||||
private let duration: Double = 0.21
|
private let duration: Double = 0.21
|
||||||
var iconState: State = .search
|
public var iconState: State = .search
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(size: CGSize(width: 30.0, height: 30.0))
|
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 {
|
guard self.iconState != state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -67,20 +67,20 @@ final class MediaPickerMoreButtonNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var action: ((ASDisplayNode, ContextGesture?) -> Void)?
|
public var action: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
private let containerNode: ContextControllerSourceNode
|
private let containerNode: ContextControllerSourceNode
|
||||||
let contextSourceNode: ContextReferenceContentNode
|
public let contextSourceNode: ContextReferenceContentNode
|
||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
let iconNode: MoreIconNode
|
public let iconNode: MoreIconNode
|
||||||
|
|
||||||
var theme: PresentationTheme {
|
public var theme: PresentationTheme {
|
||||||
didSet {
|
didSet {
|
||||||
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
|
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: PresentationTheme) {
|
public init(theme: PresentationTheme) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.contextSourceNode = ContextReferenceContentNode()
|
self.contextSourceNode = ContextReferenceContentNode()
|
@ -115,7 +115,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private let collection: PHAssetCollection?
|
private let collection: PHAssetCollection?
|
||||||
|
|
||||||
private let titleView: MediaPickerTitleView
|
private let titleView: MediaPickerTitleView
|
||||||
private let moreButtonNode: MediaPickerMoreButtonNode
|
private let moreButtonNode: MoreButtonNode
|
||||||
|
|
||||||
public weak var webSearchController: WebSearchController?
|
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 = 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.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))
|
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 isCreator {
|
||||||
if isGroup {
|
if isGroup {
|
||||||
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
|
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
|
||||||
@ -747,24 +741,18 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
|
|
||||||
let isGroup = true
|
let isGroup = true
|
||||||
let isChannel = false
|
let isChannel = false
|
||||||
var maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific
|
let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific
|
||||||
let rightsOrder: [TelegramChatAdminRightsFlags] = [
|
let rightsOrder: [TelegramChatAdminRightsFlags] = [
|
||||||
.canChangeInfo,
|
.canChangeInfo,
|
||||||
.canDeleteMessages,
|
.canDeleteMessages,
|
||||||
.canBanUsers,
|
.canBanUsers,
|
||||||
.canInviteUsers,
|
.canInviteUsers,
|
||||||
.canPinMessages,
|
.canPinMessages,
|
||||||
.canManageCalls,
|
.canManageCalls,
|
||||||
.canBeAnonymous,
|
.canBeAnonymous,
|
||||||
.canAddAdmins
|
.canAddAdmins
|
||||||
]
|
]
|
||||||
|
|
||||||
if invite {
|
|
||||||
maskRightsFlags.remove(.canManageCalls)
|
|
||||||
maskRightsFlags.remove(.canBeAnonymous)
|
|
||||||
maskRightsFlags.remove(.canAddAdmins)
|
|
||||||
}
|
|
||||||
|
|
||||||
let accountUserRightsFlags: TelegramChatAdminRightsFlags = maskRightsFlags
|
let accountUserRightsFlags: TelegramChatAdminRightsFlags = maskRightsFlags
|
||||||
|
|
||||||
let currentRightsFlags: TelegramChatAdminRightsFlags
|
let currentRightsFlags: TelegramChatAdminRightsFlags
|
||||||
@ -812,13 +800,19 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
return entries
|
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 statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: ChannelAdminControllerState())
|
let stateValue = Atomic(value: ChannelAdminControllerState())
|
||||||
let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in
|
let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let initialAdminRights = initialAdminRights {
|
||||||
|
updateState {
|
||||||
|
return $0.withUpdatedUpdatedFlags(initialAdminRights)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
let updateRightsDisposable = MetaDisposable()
|
let updateRightsDisposable = MetaDisposable()
|
||||||
|
@ -14,7 +14,7 @@ import PhoneNumberFormat
|
|||||||
import CoreTelephony
|
import CoreTelephony
|
||||||
import MessageUI
|
import MessageUI
|
||||||
|
|
||||||
final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate {
|
public final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate {
|
||||||
private var controllerNode: ChangePhoneNumberControllerNode {
|
private var controllerNode: ChangePhoneNumberControllerNode {
|
||||||
return self.displayNode as! ChangePhoneNumberControllerNode
|
return self.displayNode as! ChangePhoneNumberControllerNode
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro
|
|||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
init(context: AccountContext) {
|
public init(context: AccountContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
@ -98,19 +98,19 @@ final class ChangePhoneNumberController: ViewController, MFMailComposeViewContro
|
|||||||
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override public func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
self.controllerNode.activateInput()
|
self.controllerNode.activateInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
self.controllerNode.activateInput()
|
self.controllerNode.activateInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, 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))
|
pushControllerImpl?(storageUsageController(context: context))
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}, changePhoneNumber: {
|
}, changePhoneNumber: {
|
||||||
pushControllerImpl?(ChangePhoneNumberIntroController(context: context, phoneNumber: phoneNumber))
|
let introController = PrivacyIntroController(context: context, mode: .changePhoneNumber(phoneNumber), proceedAction: {
|
||||||
|
replaceTopControllerImpl?(ChangePhoneNumberController(context: context))
|
||||||
|
})
|
||||||
|
pushControllerImpl?(introController)
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}, contactSupport: { [weak navigationController] in
|
}, contactSupport: { [weak navigationController] in
|
||||||
let supportPeer = Promise<PeerId?>()
|
let supportPeer = Promise<PeerId?>()
|
||||||
|
@ -8,15 +8,19 @@ import TelegramCore
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import PhoneNumberFormat
|
||||||
|
|
||||||
enum PrivacyIntroControllerMode {
|
public enum PrivacyIntroControllerMode {
|
||||||
case passcode
|
case passcode
|
||||||
case twoStepVerification
|
case twoStepVerification
|
||||||
|
case changePhoneNumber(String)
|
||||||
|
|
||||||
var animationName: String? {
|
var animationName: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .passcode:
|
case .passcode:
|
||||||
return "Passcode"
|
return "Passcode"
|
||||||
|
case .changePhoneNumber:
|
||||||
|
return "ChangePhoneNumber"
|
||||||
case .twoStepVerification:
|
case .twoStepVerification:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -24,10 +28,8 @@ enum PrivacyIntroControllerMode {
|
|||||||
|
|
||||||
func icon(theme: PresentationTheme) -> UIImage? {
|
func icon(theme: PresentationTheme) -> UIImage? {
|
||||||
switch self {
|
switch self {
|
||||||
case .passcode:
|
case .passcode, .changePhoneNumber, .twoStepVerification:
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeIntroIcon"), color: theme.list.freeTextColor)
|
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
|
return strings.PasscodeSettings_Title
|
||||||
case .twoStepVerification:
|
case .twoStepVerification:
|
||||||
return strings.PrivacySettings_TwoStepAuth
|
return strings.PrivacySettings_TwoStepAuth
|
||||||
|
case .changePhoneNumber:
|
||||||
|
return strings.ChangePhoneNumberNumber_Title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +50,8 @@ enum PrivacyIntroControllerMode {
|
|||||||
return strings.PasscodeSettings_Title
|
return strings.PasscodeSettings_Title
|
||||||
case .twoStepVerification:
|
case .twoStepVerification:
|
||||||
return strings.TwoStepAuth_AdditionalPassword
|
return strings.TwoStepAuth_AdditionalPassword
|
||||||
|
case let .changePhoneNumber(phoneNumber):
|
||||||
|
return formatPhoneNumber(phoneNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,15 +61,19 @@ enum PrivacyIntroControllerMode {
|
|||||||
return strings.PasscodeSettings_HelpTop
|
return strings.PasscodeSettings_HelpTop
|
||||||
case .twoStepVerification:
|
case .twoStepVerification:
|
||||||
return strings.TwoStepAuth_SetPasswordHelp
|
return strings.TwoStepAuth_SetPasswordHelp
|
||||||
|
case .changePhoneNumber:
|
||||||
|
return strings.PhoneNumberHelp_Help
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buttonTitle(strings: PresentationStrings) -> String {
|
func buttonTitle(strings: PresentationStrings) -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .passcode:
|
case .passcode:
|
||||||
return strings.PasscodeSettings_TurnPasscodeOn
|
return strings.PasscodeSettings_TurnPasscodeOn
|
||||||
case .twoStepVerification:
|
case .twoStepVerification:
|
||||||
return strings.TwoStepAuth_SetPassword
|
return strings.TwoStepAuth_SetPassword
|
||||||
|
case .changePhoneNumber:
|
||||||
|
return strings.PhoneNumberHelp_ChangeNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +81,7 @@ enum PrivacyIntroControllerMode {
|
|||||||
switch self {
|
switch self {
|
||||||
case .passcode:
|
case .passcode:
|
||||||
return strings.PasscodeSettings_HelpBottom
|
return strings.PasscodeSettings_HelpBottom
|
||||||
case .twoStepVerification:
|
case .twoStepVerification, .changePhoneNumber:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +97,7 @@ public final class PrivacyIntroControllerPresentationArguments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PrivacyIntroController: ViewController {
|
public final class PrivacyIntroController: ViewController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let mode: PrivacyIntroControllerMode
|
private let mode: PrivacyIntroControllerMode
|
||||||
private let arguments: PrivacyIntroControllerPresentationArguments
|
private let arguments: PrivacyIntroControllerPresentationArguments
|
||||||
@ -102,7 +112,7 @@ final class PrivacyIntroController: ViewController {
|
|||||||
|
|
||||||
private var isDismissed: Bool = false
|
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.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
@ -116,6 +126,8 @@ final class PrivacyIntroController: ViewController {
|
|||||||
|
|
||||||
self.title = self.mode.controllerTitle(strings: self.presentationData.strings)
|
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 {
|
if arguments.animateIn {
|
||||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
|
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)
|
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
if self.arguments.animateIn {
|
if self.arguments.animateIn {
|
||||||
self.controllerNode.animateIn(slide: true)
|
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 {
|
if !self.isDismissed {
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
if self.arguments.animateIn {
|
if self.arguments.animateIn {
|
||||||
|
@ -193,7 +193,9 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool
|
|||||||
return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? ""
|
return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? ""
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { phoneNumber in
|
|> 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
|
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 urlAuth(url: String, buttonId: Int32)
|
||||||
case setupPoll(isQuiz: Bool?)
|
case setupPoll(isQuiz: Bool?)
|
||||||
case openUserProfile(peerId: PeerId)
|
case openUserProfile(peerId: PeerId)
|
||||||
case addToChat
|
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||||
@ -38,8 +37,6 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
|||||||
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
|
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
|
||||||
case 10:
|
case 10:
|
||||||
self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
|
self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
|
||||||
case 11:
|
|
||||||
self = .addToChat
|
|
||||||
default:
|
default:
|
||||||
self = .text
|
self = .text
|
||||||
}
|
}
|
||||||
@ -82,8 +79,6 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
|||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
encoder.encodeInt32(10, forKey: "v")
|
encoder.encodeInt32(10, forKey: "v")
|
||||||
encoder.encodeInt64(peerId.toInt64(), forKey: "peerId")
|
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 chatBubbleActionButtonIncomingPaymentIconImage: UIImage
|
||||||
public let chatBubbleActionButtonIncomingProfileIconImage: UIImage
|
public let chatBubbleActionButtonIncomingProfileIconImage: UIImage
|
||||||
public let chatBubbleActionButtonIncomingAddToChatIconImage: UIImage
|
public let chatBubbleActionButtonIncomingAddToChatIconImage: UIImage
|
||||||
|
public let chatBubbleActionButtonIncomingWebAppIconImage: UIImage
|
||||||
|
|
||||||
public let chatBubbleActionButtonOutgoingMessageIconImage: UIImage
|
public let chatBubbleActionButtonOutgoingMessageIconImage: UIImage
|
||||||
public let chatBubbleActionButtonOutgoingLinkIconImage: UIImage
|
public let chatBubbleActionButtonOutgoingLinkIconImage: UIImage
|
||||||
@ -550,6 +551,7 @@ public final class PrincipalThemeAdditionalGraphics {
|
|||||||
public let chatBubbleActionButtonOutgoingPaymentIconImage: UIImage
|
public let chatBubbleActionButtonOutgoingPaymentIconImage: UIImage
|
||||||
public let chatBubbleActionButtonOutgoingProfileIconImage: UIImage
|
public let chatBubbleActionButtonOutgoingProfileIconImage: UIImage
|
||||||
public let chatBubbleActionButtonOutgoingAddToChatIconImage: UIImage
|
public let chatBubbleActionButtonOutgoingAddToChatIconImage: UIImage
|
||||||
|
public let chatBubbleActionButtonOutgoingWebAppIconImage: UIImage
|
||||||
|
|
||||||
public let chatEmptyItemLockIcon: UIImage
|
public let chatEmptyItemLockIcon: UIImage
|
||||||
public let emptyChatListCheckIcon: 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.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.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.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.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.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))!
|
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.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.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.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
|
self.chatEmptyItemLockIcon = generateImage(CGSize(width: 9.0, height: 13.0), rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Snowflake.png",
|
"filename" : "Tmp.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"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" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"provides-namespace" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"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)
|
self.controllerInteraction.openPollCreation(isQuiz)
|
||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
self.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
self.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
||||||
case .addToChat:
|
|
||||||
self.controllerInteraction.openAddToChat()
|
|
||||||
}
|
}
|
||||||
if dismissIfOnce {
|
if dismissIfOnce {
|
||||||
if let message = self.message {
|
if let message = self.message {
|
||||||
|
@ -75,6 +75,7 @@ import ChatPresentationInterfaceState
|
|||||||
import Pasteboard
|
import Pasteboard
|
||||||
import ChatSendMessageActionUI
|
import ChatSendMessageActionUI
|
||||||
import ChatTextLinkEditUI
|
import ChatTextLinkEditUI
|
||||||
|
import WebUI
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
import os.signpost
|
import os.signpost
|
||||||
@ -3298,23 +3299,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
|
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
|
}, requestMessageUpdate: { [weak self] id in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||||
@ -10435,6 +10419,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if canSendPolls {
|
if canSendPolls {
|
||||||
availableTabs.insert(.poll, at: availableTabs.count - 1)
|
availableTabs.insert(.poll, at: availableTabs.count - 1)
|
||||||
}
|
}
|
||||||
|
// availableTabs.insert(.app("Web App"), at: 1)
|
||||||
|
|
||||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||||
|
|
||||||
@ -10700,7 +10685,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
completion(controller, nil)
|
completion(controller, nil)
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
case .app:
|
case .app:
|
||||||
return
|
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, url: "", message: nil)
|
||||||
|
completion(controller, nil)
|
||||||
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let present = {
|
let present = {
|
||||||
|
@ -131,7 +131,6 @@ public final class ChatControllerInteraction {
|
|||||||
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
|
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
|
||||||
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
|
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
|
||||||
let openJoinLink: (String) -> Void
|
let openJoinLink: (String) -> Void
|
||||||
let openAddToChat: () -> Void
|
|
||||||
|
|
||||||
let requestMessageUpdate: (MessageId) -> Void
|
let requestMessageUpdate: (MessageId) -> Void
|
||||||
let cancelInteractiveKeyboardGestures: () -> Void
|
let cancelInteractiveKeyboardGestures: () -> Void
|
||||||
@ -231,7 +230,6 @@ public final class ChatControllerInteraction {
|
|||||||
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
|
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
|
||||||
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
|
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
|
||||||
openJoinLink: @escaping (String) -> Void,
|
openJoinLink: @escaping (String) -> Void,
|
||||||
openAddToChat: @escaping () -> Void,
|
|
||||||
requestMessageUpdate: @escaping (MessageId) -> Void,
|
requestMessageUpdate: @escaping (MessageId) -> Void,
|
||||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||||
@ -317,7 +315,6 @@ public final class ChatControllerInteraction {
|
|||||||
self.commitEmojiInteraction = commitEmojiInteraction
|
self.commitEmojiInteraction = commitEmojiInteraction
|
||||||
self.openLargeEmojiInfo = openLargeEmojiInfo
|
self.openLargeEmojiInfo = openLargeEmojiInfo
|
||||||
self.openJoinLink = openJoinLink
|
self.openJoinLink = openJoinLink
|
||||||
self.openAddToChat = openAddToChat
|
|
||||||
self.requestMessageUpdate = requestMessageUpdate
|
self.requestMessageUpdate = requestMessageUpdate
|
||||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||||
|
|
||||||
@ -377,7 +374,6 @@ public final class ChatControllerInteraction {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
@ -94,7 +94,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
switch button.action {
|
switch button.action {
|
||||||
case .text:
|
case .text:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage
|
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
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingLinkIconImage : graphics.chatBubbleActionButtonOutgoingLinkIconImage
|
||||||
case .requestPhone:
|
case .requestPhone:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPhoneIconImage : graphics.chatBubbleActionButtonOutgoingPhoneIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPhoneIconImage : graphics.chatBubbleActionButtonOutgoingPhoneIconImage
|
||||||
@ -106,8 +112,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
|
||||||
case .openUserProfile:
|
case .openUserProfile:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
|
||||||
case .addToChat:
|
case .openWebApp:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingAddToChatIconImage : graphics.chatBubbleActionButtonOutgoingAddToChatIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage
|
||||||
default:
|
default:
|
||||||
iconImage = nil
|
iconImage = nil
|
||||||
}
|
}
|
||||||
|
@ -854,8 +854,6 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
|
|||||||
break
|
break
|
||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
item.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
item.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
||||||
case .addToChat:
|
|
||||||
item.controllerInteraction.openAddToChat()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -532,7 +532,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||||
|
@ -159,7 +159,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, 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)
|
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):
|
case let .botStart(peerId, payload):
|
||||||
openPeer(peerId, .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
|
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))
|
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
|
controller.peerSelected = { [weak controller] peer in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
@ -132,7 +132,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
|
|
||||||
if let peer = peer as? TelegramChannel {
|
if let peer = peer as? TelegramChannel {
|
||||||
if peer.flags.contains(.isCreator) || peer.adminRights != nil {
|
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()
|
controller?.dismiss()
|
||||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||||
navigationController?.pushViewController(controller)
|
navigationController?.pushViewController(controller)
|
||||||
@ -143,7 +143,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
if case .member = peer.role {
|
if case .member = peer.role {
|
||||||
addMemberImpl()
|
addMemberImpl()
|
||||||
} else {
|
} 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()
|
controller?.dismiss()
|
||||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||||
navigationController?.pushViewController(controller)
|
navigationController?.pushViewController(controller)
|
||||||
|
@ -610,6 +610,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
var domain: String?
|
var domain: String?
|
||||||
var start: String?
|
var start: String?
|
||||||
var startGroup: String?
|
var startGroup: String?
|
||||||
|
var admin: String?
|
||||||
var game: String?
|
var game: String?
|
||||||
var post: String?
|
var post: String?
|
||||||
var voiceChat: String?
|
var voiceChat: String?
|
||||||
@ -624,6 +625,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
start = value
|
start = value
|
||||||
} else if queryItem.name == "startgroup" {
|
} else if queryItem.name == "startgroup" {
|
||||||
startGroup = value
|
startGroup = value
|
||||||
|
} else if queryItem.name == "admin" {
|
||||||
|
admin = value
|
||||||
} else if queryItem.name == "game" {
|
} else if queryItem.name == "game" {
|
||||||
game = value
|
game = value
|
||||||
} else if queryItem.name == "post" {
|
} else if queryItem.name == "post" {
|
||||||
@ -648,6 +651,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
result += "?start=\(start)"
|
result += "?start=\(start)"
|
||||||
} else if let startGroup = startGroup {
|
} else if let startGroup = startGroup {
|
||||||
result += "?startgroup=\(startGroup)"
|
result += "?startgroup=\(startGroup)"
|
||||||
|
if let admin = admin {
|
||||||
|
result += "&admin=\(admin)"
|
||||||
|
}
|
||||||
} else if let game = game {
|
} else if let game = game {
|
||||||
result += "?game=\(game)"
|
result += "?game=\(game)"
|
||||||
} else if let voiceChat = voiceChat {
|
} else if let voiceChat = voiceChat {
|
||||||
|
@ -151,7 +151,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))
|
||||||
|
@ -2265,7 +2265,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
@ -4787,7 +4786,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
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,
|
}, sendFile: nil,
|
||||||
sendSticker: nil,
|
sendSticker: nil,
|
||||||
requestMessageActionUrlAuth: nil,
|
requestMessageActionUrlAuth: nil,
|
||||||
@ -5648,7 +5647,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self.openTips()
|
self.openTips()
|
||||||
case .phoneNumber:
|
case .phoneNumber:
|
||||||
if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone {
|
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:
|
case .username:
|
||||||
self.controller?.push(usernameSetupController(context: self.context))
|
self.controller?.push(usernameSetupController(context: self.context))
|
||||||
|
@ -1325,7 +1325,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openAddToChat: {
|
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
@ -19,9 +19,53 @@ private let baseTelegraPhPaths = [
|
|||||||
"telegram.org/tour/"
|
"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 {
|
public enum ParsedInternalPeerUrlParameter {
|
||||||
case botStart(String)
|
case botStart(String)
|
||||||
case groupBotStart(String)
|
case groupBotStart(String, ResolvedBotAdminRights?)
|
||||||
case channelMessage(Int32, Double?)
|
case channelMessage(Int32, Double?)
|
||||||
case replyThread(Int32, Int32)
|
case replyThread(Int32, Int32)
|
||||||
case voiceChat(String?)
|
case voiceChat(String?)
|
||||||
@ -141,7 +185,14 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if queryItem.name == "start" {
|
if queryItem.name == "start" {
|
||||||
return .peerName(peerName, .botStart(value))
|
return .peerName(peerName, .botStart(value))
|
||||||
} else if queryItem.name == "startgroup" {
|
} 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" {
|
} else if queryItem.name == "game" {
|
||||||
return nil
|
return nil
|
||||||
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
||||||
@ -378,8 +429,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
switch parameter {
|
switch parameter {
|
||||||
case let .botStart(payload):
|
case let .botStart(payload):
|
||||||
return .single(.botStart(peerId: peer.id, payload: payload))
|
return .single(.botStart(peerId: peer.id, payload: payload))
|
||||||
case let .groupBotStart(payload):
|
case let .groupBotStart(payload, adminRights):
|
||||||
return .single(.groupBotStart(peerId: peer.id, payload: payload))
|
return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights))
|
||||||
case let .channelMessage(id, timecode):
|
case let .channelMessage(id, timecode):
|
||||||
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: 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):
|
case let .replyThread(id, replyId):
|
||||||
|
@ -36,7 +36,7 @@ public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concea
|
|||||||
var parsedUrlValue: URL?
|
var parsedUrlValue: URL?
|
||||||
if url.hasPrefix("tel:") {
|
if url.hasPrefix("tel:") {
|
||||||
return (url, false)
|
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
|
parsedUrlValue = parsed
|
||||||
} else if let parsed = URL(string: "https://" + url) {
|
} else if let parsed = URL(string: "https://" + url) {
|
||||||
parsedUrlValue = parsed
|
parsedUrlValue = parsed
|
||||||
|
@ -10,8 +10,14 @@ swift_library(
|
|||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||||
"//submodules/Display:Display",
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/AttachmentUI:AttachmentUI",
|
||||||
|
"//submodules/CounterContollerTitleView:CounterContollerTitleView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|