mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
85b6a8ffe9
commit
ef4d0adbb9
16 changed files with 571 additions and 344 deletions
|
|
@ -905,7 +905,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
}
|
||||
} else {
|
||||
let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in
|
||||
|
||||
let _ = tempFile
|
||||
})
|
||||
self.present(controller, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) }
|
||||
dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) }
|
||||
dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) }
|
||||
dict[1636301421] = { return Api.messages.PreparedInlineMessage.parse_preparedInlineMessage($0) }
|
||||
dict[-11046771] = { return Api.messages.PreparedInlineMessage.parse_preparedInlineMessage($0) }
|
||||
dict[-963811691] = { return Api.messages.QuickReplies.parse_quickReplies($0) }
|
||||
dict[1603398491] = { return Api.messages.QuickReplies.parse_quickRepliesNotModified($0) }
|
||||
dict[-352454890] = { return Api.messages.Reactions.parse_reactions($0) }
|
||||
|
|
|
|||
|
|
@ -866,13 +866,13 @@ public extension Api.messages {
|
|||
}
|
||||
public extension Api.messages {
|
||||
enum PreparedInlineMessage: TypeConstructorDescription {
|
||||
case preparedInlineMessage(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], users: [Api.User])
|
||||
case preparedInlineMessage(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], cacheTime: Int32, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .preparedInlineMessage(let queryId, let result, let peerTypes, let users):
|
||||
case .preparedInlineMessage(let queryId, let result, let peerTypes, let cacheTime, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(1636301421)
|
||||
buffer.appendInt32(-11046771)
|
||||
}
|
||||
serializeInt64(queryId, buffer: buffer, boxed: false)
|
||||
result.serialize(buffer, true)
|
||||
|
|
@ -881,6 +881,7 @@ public extension Api.messages {
|
|||
for item in peerTypes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
serializeInt32(cacheTime, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
|
|
@ -892,8 +893,8 @@ public extension Api.messages {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .preparedInlineMessage(let queryId, let result, let peerTypes, let users):
|
||||
return ("preparedInlineMessage", [("queryId", queryId as Any), ("result", result as Any), ("peerTypes", peerTypes as Any), ("users", users as Any)])
|
||||
case .preparedInlineMessage(let queryId, let result, let peerTypes, let cacheTime, let users):
|
||||
return ("preparedInlineMessage", [("queryId", queryId as Any), ("result", result as Any), ("peerTypes", peerTypes as Any), ("cacheTime", cacheTime as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -908,16 +909,19 @@ public extension Api.messages {
|
|||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self)
|
||||
}
|
||||
var _4: [Api.User]?
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, users: _4!)
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -3,19 +3,23 @@ import Postbox
|
|||
import TelegramApi
|
||||
|
||||
extension ReplyMarkupButtonAction.PeerTypes {
|
||||
init?(apiType: Api.InlineQueryPeerType) {
|
||||
switch apiType {
|
||||
case .inlineQueryPeerTypePM:
|
||||
self = .users
|
||||
case .inlineQueryPeerTypeBotPM:
|
||||
self = .bots
|
||||
case .inlineQueryPeerTypeBroadcast:
|
||||
self = .channels
|
||||
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
|
||||
self = .groups
|
||||
case .inlineQueryPeerTypeSameBotPM:
|
||||
return nil
|
||||
init(apiType: [Api.InlineQueryPeerType]) {
|
||||
var rawValue: Int32 = 0
|
||||
for type in apiType {
|
||||
switch type {
|
||||
case .inlineQueryPeerTypePM:
|
||||
rawValue |= ReplyMarkupButtonAction.PeerTypes.users.rawValue
|
||||
case .inlineQueryPeerTypeBotPM:
|
||||
rawValue |= ReplyMarkupButtonAction.PeerTypes.bots.rawValue
|
||||
case .inlineQueryPeerTypeBroadcast:
|
||||
rawValue |= ReplyMarkupButtonAction.PeerTypes.channels.rawValue
|
||||
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
|
||||
rawValue |= ReplyMarkupButtonAction.PeerTypes.groups.rawValue
|
||||
case .inlineQueryPeerTypeSameBotPM:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2162,6 +2162,34 @@ public extension TelegramEngine.EngineData.Item {
|
|||
}
|
||||
}
|
||||
|
||||
public struct BotAppSettings: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Optional<TelegramCore.BotAppSettings>
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||
return cachedData.botInfo?.appSettings
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotCommands: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Optional<[BotCommand]>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import TelegramApi
|
|||
import MtProtoKit
|
||||
|
||||
public struct PreparedInlineMessage: Equatable {
|
||||
let queryId: Int64
|
||||
let result: ChatContextResult
|
||||
let peerTypes: [ReplyMarkupButtonAction.PeerTypes]
|
||||
public let botId: EnginePeer.Id
|
||||
public let queryId: Int64
|
||||
public let result: ChatContextResult
|
||||
public let peerTypes: ReplyMarkupButtonAction.PeerTypes
|
||||
}
|
||||
|
||||
func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id, id: String) -> Signal<PreparedInlineMessage?, NoError> {
|
||||
|
|
@ -29,10 +30,15 @@ func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id,
|
|||
}
|
||||
return account.postbox.transaction { transaction -> PreparedInlineMessage? in
|
||||
switch result {
|
||||
case let .preparedInlineMessage(queryId, result, peerTypes, users):
|
||||
case let .preparedInlineMessage(queryId, result, apiPeerTypes, cacheTime, users):
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: users))
|
||||
|
||||
return PreparedInlineMessage(queryId: queryId, result: ChatContextResult(apiResult: result, queryId: queryId), peerTypes: peerTypes.compactMap { ReplyMarkupButtonAction.PeerTypes(apiType: $0) })
|
||||
let _ = cacheTime
|
||||
return PreparedInlineMessage(
|
||||
botId: botId,
|
||||
queryId: queryId,
|
||||
result: ChatContextResult(apiResult: result, queryId: queryId),
|
||||
peerTypes: ReplyMarkupButtonAction.PeerTypes(apiType: apiPeerTypes)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public enum EngineOutgoingMessageContent {
|
|||
case text(String, [MessageTextEntity])
|
||||
case file(FileMediaReference)
|
||||
case contextResult(ChatContextResultCollection, ChatContextResult)
|
||||
case preparedInlineMessage(PreparedInlineMessage)
|
||||
}
|
||||
|
||||
public final class StoryPreloadInfo {
|
||||
|
|
@ -249,7 +250,9 @@ public extension TelegramEngine {
|
|||
scheduleTime: Int32? = nil
|
||||
) -> Signal<[MessageId?], NoError> {
|
||||
var message: EnqueueMessage?
|
||||
if case let .contextResult(results, result) = content {
|
||||
if case let .preparedInlineMessage(preparedInlineMessage) = content {
|
||||
message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil)
|
||||
} else if case let .contextResult(results, result) = content {
|
||||
message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil)
|
||||
} else {
|
||||
var attributes: [MessageAttribute] = []
|
||||
|
|
|
|||
|
|
@ -1610,6 +1610,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
}
|
||||
}
|
||||
|
||||
if isPreview, let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramUser, peer.firstName == nil {
|
||||
hasAvatar = false
|
||||
effectiveAuthor = nil
|
||||
}
|
||||
|
||||
var isInstantVideo = false
|
||||
if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser {
|
||||
for media in item.content.firstMessage.media {
|
||||
|
|
|
|||
|
|
@ -1555,7 +1555,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||
|> deliverOnMainQueue).startStandalone()
|
||||
}))
|
||||
|
||||
if data.webAppPermissions?.location?.isRequested == true {
|
||||
if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true {
|
||||
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: "Geolocation", value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in
|
||||
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in
|
||||
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value))
|
||||
|
|
@ -5459,6 +5459,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
}
|
||||
|
||||
var appSettings: BotAppSettings?
|
||||
if let settings = self.data?.cachedData as? CachedUserData {
|
||||
appSettings = settings.botInfo?.appSettings
|
||||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
let proceed: (Bool) -> Void = { [weak self] installed in
|
||||
guard let self else {
|
||||
|
|
@ -5466,7 +5471,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
let context = self.context
|
||||
let peerId = self.peerId
|
||||
let params = WebAppParameters(source: .settings, peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: bot.flags.contains(.hasSettings), fullSize: true)
|
||||
let params = WebAppParameters(source: .settings, peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, botVerified: bot.peer.isVerified, botAddress: bot.peer.addressName ?? "", url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: bot.flags.contains(.hasSettings), fullSize: true, appSettings: appSettings)
|
||||
|
||||
var openUrlImpl: ((String, Bool, Bool, @escaping () -> Void) -> Void)?
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
|
|
|
|||
|
|
@ -94,187 +94,72 @@ func openWebAppImpl(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
let openWebView = { [weak parentController] in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
if source == .menu {
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(true)
|
||||
}
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotAppSettings(id: botPeer.id))
|
||||
|> deliverOnMainQueue).start(next: { appSettings in
|
||||
let openWebView = { [weak parentController] in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
|
||||
if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
for controller in minimizedContainer.controllers {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == botPeer.id && mainController.source == .menu {
|
||||
navigationController.maximizeViewController(controller, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fullSize = false
|
||||
var isFullscreen = false
|
||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
if url.contains("mode=fullscreen") {
|
||||
isFullscreen = true
|
||||
fullSize = true
|
||||
} else {
|
||||
fullSize = !url.contains("mode=compact")
|
||||
}
|
||||
}
|
||||
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen)
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getInputContainerNode: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl, let layout = parentController.validLayout, case .compact = layout.metrics.widthClass {
|
||||
return (parentController.chatDisplayNode.getWindowInputAccessoryHeight(), parentController.chatDisplayNode.inputPanelContainerNode, {
|
||||
return parentController.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, completion: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
}, willDismiss: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.interfaceInteraction?.updateShowWebView { _ in
|
||||
return false
|
||||
}
|
||||
}
|
||||
}, didDismiss: { [weak parentController] in
|
||||
if source == .menu {
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(false)
|
||||
return state.updatedForceInputCommandsHidden(true)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
navigationController = parentController.effectiveNavigationController
|
||||
}
|
||||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
parentController.push(controller)
|
||||
|
||||
presentImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
} else if simple {
|
||||
var isInline = false
|
||||
var botId = botPeer.id
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
var botVerified = botPeer.isVerified
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
botId = bot.id
|
||||
botName = bot.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
botAddress = bot.addressName ?? ""
|
||||
botVerified = bot.isVerified
|
||||
}
|
||||
|
||||
let messageActionCallbackDisposable: MetaDisposable
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
messageActionCallbackDisposable = parentController.messageActionCallbackDisposable
|
||||
} else {
|
||||
messageActionCallbackDisposable = MetaDisposable()
|
||||
}
|
||||
|
||||
let webViewSignal: Signal<RequestWebViewResult, RequestWebViewError>
|
||||
let webViewSource: RequestSimpleWebViewSource
|
||||
if let payload {
|
||||
webViewSource = .inline(startParam: payload)
|
||||
} else {
|
||||
webViewSource = .generic
|
||||
}
|
||||
if url.isEmpty {
|
||||
webViewSignal = context.engine.messages.requestMainWebView(peerId: chatPeer?.id ?? botId, botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
} else {
|
||||
webViewSignal = context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
}
|
||||
|
||||
messageActionCallbackDisposable.set(((webViewSignal
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { [weak parentController] result in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let source: WebAppParameters.Source
|
||||
if isInline {
|
||||
source = .inline
|
||||
} else {
|
||||
source = url.isEmpty ? .generic : .simple
|
||||
}
|
||||
let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
navigationController = parentController.effectiveNavigationController
|
||||
}
|
||||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.currentWebAppController = controller
|
||||
}
|
||||
parentController.push(controller)
|
||||
|
||||
presentImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
for controller in minimizedContainer.controllers {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == botPeer.id && mainController.source == .menu {
|
||||
navigationController.maximizeViewController(controller, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}, error: { [weak parentController] error in
|
||||
if let parentController {
|
||||
parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
let messageActionCallbackDisposable: MetaDisposable
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
messageActionCallbackDisposable = parentController.messageActionCallbackDisposable
|
||||
} else {
|
||||
messageActionCallbackDisposable = MetaDisposable()
|
||||
}
|
||||
|
||||
messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in
|
||||
guard let parentController else {
|
||||
return
|
||||
|
||||
var fullSize = false
|
||||
var isFullscreen = false
|
||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
if url.contains("mode=fullscreen") {
|
||||
isFullscreen = true
|
||||
fullSize = true
|
||||
} else {
|
||||
fullSize = !url.contains("mode=compact")
|
||||
}
|
||||
}
|
||||
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen, appSettings: appSettings)
|
||||
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getInputContainerNode: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl, let layout = parentController.validLayout, case .compact = layout.metrics.widthClass {
|
||||
return (parentController.chatDisplayNode.getWindowInputAccessoryHeight(), parentController.chatDisplayNode.inputPanelContainerNode, {
|
||||
return parentController.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, completion: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
}, willDismiss: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.interfaceInteraction?.updateShowWebView { _ in
|
||||
return false
|
||||
}
|
||||
}
|
||||
}, didDismiss: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(false)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
|
|
@ -283,51 +168,169 @@ func openWebAppImpl(
|
|||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.currentWebAppController = controller
|
||||
}
|
||||
parentController.push(controller)
|
||||
|
||||
presentImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
}, error: { [weak parentController] error in
|
||||
if let parentController {
|
||||
parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
} else if simple {
|
||||
var isInline = false
|
||||
var botId = botPeer.id
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
var botVerified = botPeer.isVerified
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
botId = bot.id
|
||||
botName = bot.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
botAddress = bot.addressName ?? ""
|
||||
botVerified = bot.isVerified
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if skipTermsOfService {
|
||||
openWebView()
|
||||
} else {
|
||||
var botPeer = botPeer
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
|
||||
if value {
|
||||
openWebView()
|
||||
} else {
|
||||
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in
|
||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
||||
openWebView()
|
||||
}, showMore: nil, openTerms: {
|
||||
if let navigationController = parentController.navigationController as? NavigationController {
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
|
||||
let messageActionCallbackDisposable: MetaDisposable
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
messageActionCallbackDisposable = parentController.messageActionCallbackDisposable
|
||||
} else {
|
||||
messageActionCallbackDisposable = MetaDisposable()
|
||||
}
|
||||
|
||||
let webViewSignal: Signal<RequestWebViewResult, RequestWebViewError>
|
||||
let webViewSource: RequestSimpleWebViewSource
|
||||
if let payload {
|
||||
webViewSource = .inline(startParam: payload)
|
||||
} else {
|
||||
webViewSource = .generic
|
||||
}
|
||||
if url.isEmpty {
|
||||
webViewSignal = context.engine.messages.requestMainWebView(peerId: chatPeer?.id ?? botId, botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
} else {
|
||||
webViewSignal = context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme))
|
||||
}
|
||||
|
||||
messageActionCallbackDisposable.set(((webViewSignal
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
parentController.present(controller, in: .window(.root))
|
||||
|> deliverOnMainQueue).start(next: { [weak parentController] result in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let source: WebAppParameters.Source
|
||||
if isInline {
|
||||
source = .inline
|
||||
} else {
|
||||
source = url.isEmpty ? .generic : .simple
|
||||
}
|
||||
let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, requestSwitchInline: { [weak parentController] query, chatTypes, completion in
|
||||
ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
navigationController = parentController.effectiveNavigationController
|
||||
}
|
||||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.currentWebAppController = controller
|
||||
}
|
||||
parentController.push(controller)
|
||||
|
||||
presentImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
}, error: { [weak parentController] error in
|
||||
if let parentController {
|
||||
parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
let messageActionCallbackDisposable: MetaDisposable
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
messageActionCallbackDisposable = parentController.messageActionCallbackDisposable
|
||||
} else {
|
||||
messageActionCallbackDisposable = MetaDisposable()
|
||||
}
|
||||
|
||||
messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
|
||||
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
presentImpl?(c, a)
|
||||
}, commit: commit)
|
||||
}, completion: { [weak parentController] in
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
}, getNavigationController: { [weak parentController] in
|
||||
var navigationController: NavigationController?
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
navigationController = parentController.effectiveNavigationController
|
||||
}
|
||||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
if let parentController = parentController as? ChatControllerImpl {
|
||||
parentController.currentWebAppController = controller
|
||||
}
|
||||
parentController.push(controller)
|
||||
|
||||
presentImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
}, error: { [weak parentController] error in
|
||||
if let parentController {
|
||||
parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if skipTermsOfService {
|
||||
openWebView()
|
||||
} else {
|
||||
var botPeer = botPeer
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in
|
||||
guard let parentController else {
|
||||
return
|
||||
}
|
||||
|
||||
if value {
|
||||
openWebView()
|
||||
} else {
|
||||
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in
|
||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
||||
openWebView()
|
||||
}, showMore: nil, openTerms: {
|
||||
if let navigationController = parentController.navigationController as? NavigationController {
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
})
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public extension ChatControllerImpl {
|
||||
|
|
@ -496,7 +499,7 @@ public extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen))
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: nil)
|
||||
var presentImpl: ((ViewController, Any?) -> Void)?
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, forceUpdate, commit in
|
||||
ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import TelegramNotices
|
|||
import PresentationDataUtils
|
||||
import TelegramCallsUI
|
||||
import AttachmentUI
|
||||
import WebUI
|
||||
|
||||
func updateChatPresentationInterfaceStateImpl(
|
||||
selfController: ChatControllerImpl,
|
||||
|
|
@ -591,7 +592,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||
if selfController.presentationInterfaceState.hasBirthdayToday {
|
||||
selfController.displayBirthdayTooltip()
|
||||
}
|
||||
|
||||
|
||||
if case .standard(.embedded) = selfController.presentationInterfaceState.mode, let controllerInteraction = selfController.controllerInteraction, let interfaceInteraction = selfController.interfaceInteraction {
|
||||
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(selfController.presentationInterfaceState, context: selfController.context, currentPanel: selfController.customNavigationPanelNode as? ChatTitleAccessoryPanelNode, controllerInteraction: controllerInteraction, interfaceInteraction: interfaceInteraction, force: true) {
|
||||
selfController.customNavigationPanelNode = titleAccessoryPanelNode as? ChatControllerCustomNavigationPanelNode
|
||||
|
|
@ -600,5 +601,9 @@ func updateChatPresentationInterfaceStateImpl(
|
|||
}
|
||||
}
|
||||
|
||||
if let cachedData = selfController.peerView?.cachedData as? CachedUserData, let appSettings = cachedData.botInfo?.appSettings {
|
||||
let _ = WebAppController.preloadAppPlaceholder(context: selfController.context, appSettings: appSettings).startStandalone()
|
||||
}
|
||||
|
||||
selfController.stateUpdated?(transition)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -613,7 +613,7 @@ extension ChatControllerImpl {
|
|||
payload = botPayload
|
||||
fromAttachMenu = false
|
||||
}
|
||||
let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false, isFullscreen: false)
|
||||
let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, botAddress: bot.peer.addressName ?? "", url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false, isFullscreen: false)
|
||||
let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject
|
||||
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageSubject?.messageId, threadId: strongSelf.chatLocation.threadId)
|
||||
controller.openUrl = { [weak self] url, concealed, forceUpdate, commit in
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ swift_library(
|
|||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
"//submodules/HexColor:HexColor",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/MediaResources",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/UrlHandling:UrlHandling",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import PresentationDataUtils
|
|||
import HexColor
|
||||
import ShimmerEffect
|
||||
import PhotoResources
|
||||
import MediaResources
|
||||
import LegacyComponents
|
||||
import UrlHandling
|
||||
import MoreButtonNode
|
||||
|
|
@ -33,6 +34,7 @@ import OverlayStatusController
|
|||
import TelegramUIPreferences
|
||||
import CoreMotion
|
||||
import DeviceLocationManager
|
||||
import LegacyMediaPickerUI
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
|
||||
|
|
@ -60,6 +62,7 @@ public struct WebAppParameters {
|
|||
let botId: PeerId
|
||||
let botName: String
|
||||
let botVerified: Bool
|
||||
let botAddress: String
|
||||
let url: String?
|
||||
let queryId: Int64?
|
||||
let payload: String?
|
||||
|
|
@ -68,6 +71,7 @@ public struct WebAppParameters {
|
|||
let forceHasSettings: Bool
|
||||
let fullSize: Bool
|
||||
let isFullscreen: Bool
|
||||
let appSettings: BotAppSettings?
|
||||
|
||||
public init(
|
||||
source: Source,
|
||||
|
|
@ -75,6 +79,7 @@ public struct WebAppParameters {
|
|||
botId: PeerId,
|
||||
botName: String,
|
||||
botVerified: Bool,
|
||||
botAddress: String,
|
||||
url: String?,
|
||||
queryId: Int64?,
|
||||
payload: String?,
|
||||
|
|
@ -82,25 +87,24 @@ public struct WebAppParameters {
|
|||
keepAliveSignal: Signal<Never, KeepWebViewError>?,
|
||||
forceHasSettings: Bool,
|
||||
fullSize: Bool,
|
||||
isFullscreen: Bool = false
|
||||
isFullscreen: Bool = false,
|
||||
appSettings: BotAppSettings? = nil
|
||||
) {
|
||||
self.source = source
|
||||
self.peerId = peerId
|
||||
self.botId = botId
|
||||
self.botName = botName
|
||||
self.botVerified = botVerified
|
||||
self.botAddress = botAddress
|
||||
self.url = url
|
||||
self.queryId = queryId
|
||||
self.payload = payload
|
||||
self.buttonText = buttonText
|
||||
self.keepAliveSignal = keepAliveSignal
|
||||
self.forceHasSettings = forceHasSettings
|
||||
self.fullSize = fullSize
|
||||
// #if DEBUG
|
||||
// self.isFullscreen = true
|
||||
// #else
|
||||
self.fullSize = fullSize || isFullscreen
|
||||
self.isFullscreen = isFullscreen
|
||||
// #endif
|
||||
self.appSettings = appSettings
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +243,21 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.placeholderNode = placeholderNode
|
||||
|
||||
let placeholder: Signal<(FileMediaReference, Bool)?, NoError>
|
||||
if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) {
|
||||
if let botAppSettings = controller.botAppSettings {
|
||||
Queue.mainQueue().justDispatch {
|
||||
if let backgroundColor = botAppSettings.backgroundColor {
|
||||
self.appBackgroundColor = UIColor(rgb: UInt32(bitPattern: backgroundColor))
|
||||
self.updateBackgroundColor(transition: .immediate)
|
||||
}
|
||||
if let headerColor = botAppSettings.headerColor {
|
||||
self.headerColor = UIColor(rgb: UInt32(bitPattern: headerColor))
|
||||
self.updateHeaderBackgroundColor(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let placeholderFile = controller.botAppSettings?.placeholder {
|
||||
placeholder = .single((.standalone(media: placeholderFile), false))
|
||||
} else if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) {
|
||||
placeholder = .single(nil)
|
||||
|> delay(0.05, queue: Queue.mainQueue())
|
||||
} else {
|
||||
|
|
@ -289,7 +307,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
if let fileReference = fileReference {
|
||||
let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, userLocation: .other, fileReference: fileReference).start()
|
||||
}
|
||||
strongSelf.placeholderDisposable.set((svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder)
|
||||
let _ = (svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||
if let strongSelf = self {
|
||||
let imageSize: CGSize
|
||||
|
|
@ -309,7 +327,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
strongSelf.placeholderNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}))
|
||||
|
||||
self.iconDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId))
|
||||
|
|
@ -461,9 +479,24 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
shapes = [.image(image: image, rect: CGRect(origin: CGPoint(), size: image.size))]
|
||||
placeholderSize = image.size
|
||||
}
|
||||
|
||||
let theme = self.presentationData.theme
|
||||
self.placeholderNode?.update(backgroundColor: .clear, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: placeholderSize, mask: true)
|
||||
|
||||
let foregroundColor: UIColor
|
||||
let shimmeringColor: UIColor
|
||||
if let backgroundColor = self.appBackgroundColor {
|
||||
if backgroundColor.lightness > 0.705 {
|
||||
foregroundColor = backgroundColor.mixedWith(UIColor(rgb: 0x000000), alpha: 0.05)
|
||||
shimmeringColor = UIColor.white.withAlphaComponent(0.2)
|
||||
} else {
|
||||
foregroundColor = backgroundColor.mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05)
|
||||
shimmeringColor = UIColor.white.withAlphaComponent(0.4)
|
||||
}
|
||||
} else {
|
||||
let theme = self.presentationData.theme
|
||||
foregroundColor = theme.list.mediaPlaceholderColor
|
||||
shimmeringColor = theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4)
|
||||
}
|
||||
|
||||
self.placeholderNode?.update(backgroundColor: .clear, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, shapes: shapes, horizontal: true, size: placeholderSize, mask: true)
|
||||
|
||||
return placeholderSize
|
||||
}
|
||||
|
|
@ -1097,8 +1130,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
case "web_app_set_background_color":
|
||||
if let json = json, let colorValue = json["color"] as? String, let color = UIColor(hexString: colorValue) {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
|
||||
transition.updateBackgroundColor(node: self.backgroundNode, color: color)
|
||||
self.appBackgroundColor = color
|
||||
self.updateBackgroundColor(transition: .animated(duration: 0.2, curve: .linear))
|
||||
}
|
||||
case "web_app_set_header_color":
|
||||
if let json = json {
|
||||
|
|
@ -1403,7 +1436,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
|
||||
fileprivate var needDismissConfirmation = false
|
||||
|
||||
|
||||
fileprivate var appBackgroundColor: UIColor?
|
||||
fileprivate var headerColor: UIColor?
|
||||
fileprivate var headerPrimaryTextColor: UIColor?
|
||||
private var headerColorKey: String?
|
||||
|
|
@ -1415,6 +1449,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
fileprivate let bottomPanelColorPromise = Promise<UIColor?>(nil)
|
||||
|
||||
private func updateBackgroundColor(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateBackgroundColor(node: self.backgroundNode, color: self.appBackgroundColor ?? .clear)
|
||||
}
|
||||
|
||||
private func updateHeaderBackgroundColor(transition: ContainedViewLayoutTransition) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
|
|
@ -2143,7 +2181,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self?.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"MESSAGE_EXPIRED\"}")
|
||||
return
|
||||
}
|
||||
let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, preparedMessage: preparedMessage, completion: { [weak self] result in
|
||||
let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, botAddress: controller.botAddress, preparedMessage: preparedMessage, completion: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -2162,6 +2200,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
var isMedia = true
|
||||
var title: String?
|
||||
let photoExtensions = [".jpg", ".png", ".gif", ".tiff"]
|
||||
let videoExtensions = [".mp4", ".mov"]
|
||||
|
|
@ -2182,6 +2221,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
if title == nil {
|
||||
title = "Download Document"
|
||||
isMedia = false
|
||||
}
|
||||
|
||||
let _ = (FileDownload.getFileSize(url: url)
|
||||
|
|
@ -2201,7 +2241,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}")
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: "Download", action: { [weak self] in
|
||||
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize)
|
||||
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia)
|
||||
})
|
||||
], parseMarkdown: true)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
|
|
@ -2210,7 +2250,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
|
||||
private var fileDownload: FileDownload?
|
||||
private weak var fileDownloadTooltip: UndoOverlayController?
|
||||
fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?) {
|
||||
fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?, isMedia: Bool) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
|
@ -2238,8 +2278,70 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
undoText: "Cancel"
|
||||
)
|
||||
},
|
||||
completion: { resultUrl, _ in
|
||||
|
||||
completion: { [weak self] resultUrl, _ in
|
||||
if let resultUrl, let self {
|
||||
if let tooltip = self.fileDownloadTooltip {
|
||||
tooltip.dismissWithCommitAction()
|
||||
}
|
||||
|
||||
if isMedia {
|
||||
let saveToPhotos: (URL, Bool) -> Void = { url, isVideo in
|
||||
var fileExtension = (resultUrl.absoluteString as NSString).pathExtension
|
||||
if fileExtension.isEmpty {
|
||||
fileExtension = "mp4"
|
||||
}
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
if isVideo {
|
||||
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
|
||||
} else {
|
||||
if let fileData = try? Data(contentsOf: url) {
|
||||
PHAssetCreationRequest.forAsset().addResource(with: .photo, data: fileData, options: nil)
|
||||
}
|
||||
}
|
||||
}, completionHandler: { _, error in
|
||||
})
|
||||
}
|
||||
let isVideo = fileName.lowercased().hasSuffix(".mp4") || fileName.lowercased().hasSuffix(".mov")
|
||||
saveToPhotos(resultUrl, isVideo)
|
||||
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .actionSucceeded(title: fileName, text: isMedia ? "Saved to Photos" : "Saved to Files", cancel: nil, destructive: false),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(tooltipController, in: .current)
|
||||
} else {
|
||||
let tempFile = TempBox.shared.file(path: resultUrl.absoluteString, fileName: fileName)
|
||||
let url = URL(fileURLWithPath: tempFile.path)
|
||||
try? FileManager.default.copyItem(at: resultUrl, to: url)
|
||||
|
||||
let pickerController = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { [weak self, weak controller] urls in
|
||||
guard let self, let controller, !urls.isEmpty else {
|
||||
return
|
||||
}
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .actionSucceeded(title: fileName, text: isMedia ? "Saved to Photos" : "Saved to Files", cancel: nil, destructive: false),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(tooltipController, in: .current)
|
||||
})
|
||||
controller.present(pickerController, in: .window(.root))
|
||||
//
|
||||
// if let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
// let targetUrl = documentsUrl.appendingPathComponent(fileName)
|
||||
// try? FileManager.default.copyItem(at: resultUrl, to: targetUrl)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -2264,7 +2366,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
return true
|
||||
}
|
||||
)
|
||||
controller.present(tooltipController, in: .window(.root))
|
||||
controller.present(tooltipController, in: .current)
|
||||
self.fileDownloadTooltip = tooltipController
|
||||
}
|
||||
|
||||
|
|
@ -2551,6 +2653,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
public let botId: PeerId
|
||||
fileprivate let botName: String
|
||||
fileprivate let botVerified: Bool
|
||||
fileprivate let botAppSettings: BotAppSettings?
|
||||
fileprivate let botAddress: String
|
||||
private let url: String?
|
||||
private let queryId: Int64?
|
||||
private let payload: String?
|
||||
|
|
@ -2579,6 +2683,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.botId = params.botId
|
||||
self.botName = params.botName
|
||||
self.botVerified = params.botVerified
|
||||
self.botAppSettings = params.appSettings
|
||||
self.botAddress = params.botAddress
|
||||
self.url = params.url
|
||||
self.queryId = params.queryId
|
||||
self.payload = params.payload
|
||||
|
|
@ -2605,6 +2711,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
if !self.isFullscreen {
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode)
|
||||
|
|
@ -2905,6 +3012,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.validLayout = layout
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
var presentationLayout = layout
|
||||
if self.isFullscreen {
|
||||
presentationLayout.intrinsicInsets.top = (presentationLayout.statusBarHeight ?? 0.0) + 36.0
|
||||
} else {
|
||||
presentationLayout.intrinsicInsets.top = 56.0
|
||||
}
|
||||
self.presentationContext.containerLayoutUpdated(presentationLayout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
|
|
@ -3015,6 +3130,30 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
})
|
||||
return imageView
|
||||
}
|
||||
|
||||
public static func preloadAppPlaceholder(context: AccountContext, appSettings: BotAppSettings) -> Signal<Never, NoError> {
|
||||
guard let file = appSettings.placeholder else {
|
||||
return .complete()
|
||||
}
|
||||
let path = context.account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation())
|
||||
if !FileManager.default.fileExists(atPath: path) {
|
||||
let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in
|
||||
let accountResource = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true)
|
||||
|
||||
let fetchedFullSize = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file), reference: .standalone(resource: file.resource))
|
||||
let fetchedFullSizeDisposable = fetchedFullSize.start()
|
||||
let fullSizeDisposable = accountResource.start()
|
||||
|
||||
return ActionDisposable {
|
||||
fetchedFullSizeDisposable.dispose()
|
||||
fullSizeDisposable.dispose()
|
||||
}
|
||||
}
|
||||
return accountFullSizeData
|
||||
|> ignoreValues
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
final class WebAppPickerContext: AttachmentMediaPickerContext {
|
||||
|
|
|
|||
|
|
@ -17,49 +17,22 @@ import ChatMessageItemImpl
|
|||
final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
|
||||
struct MessageItem: Equatable {
|
||||
static func ==(lhs: MessageItem, rhs: MessageItem) -> Bool {
|
||||
if lhs.outgoing != rhs.outgoing {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
if lhs.author != rhs.author {
|
||||
return false
|
||||
}
|
||||
if lhs.photo != rhs.photo {
|
||||
return false
|
||||
}
|
||||
if lhs.nameColor != rhs.nameColor {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundEmojiId != rhs.backgroundEmojiId {
|
||||
return false
|
||||
}
|
||||
if let lhsReply = lhs.reply, let rhsReply = rhs.reply, lhsReply.0 != rhsReply.0 || lhsReply.1 != rhsReply.1 || lhsReply.2 != rhsReply.2 {
|
||||
return false
|
||||
} else if (lhs.reply == nil) != (rhs.reply == nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsLinkPreview = lhs.linkPreview, let rhsLinkPreview = rhs.linkPreview, lhsLinkPreview.0 != rhsLinkPreview.0 || lhsLinkPreview.1 != rhsLinkPreview.1 || lhsLinkPreview.2 != rhsLinkPreview.2 {
|
||||
return false
|
||||
} else if (lhs.linkPreview == nil) != (rhs.linkPreview == nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if areMediaArraysEqual(lhs.media, rhs.media) {
|
||||
return false
|
||||
}
|
||||
if lhs.botAddress != rhs.botAddress {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let outgoing: Bool
|
||||
let peerId: EnginePeer.Id
|
||||
let author: String
|
||||
let photo: [TelegramMediaImageRepresentation]
|
||||
let nameColor: PeerNameColor
|
||||
let backgroundEmojiId: Int64?
|
||||
let reply: (String, String, PeerNameColor)?
|
||||
let linkPreview: (String, String, String)?
|
||||
let text: String
|
||||
let entities: TextEntitiesMessageAttribute?
|
||||
let media: [Media]
|
||||
let botAddress: String
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
|
|
@ -202,9 +175,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||
let currentNodes = self.messageNodes
|
||||
|
||||
var currentBackgroundNode = self.backgroundNode
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
|
||||
return { item, params, neighbors in
|
||||
if currentBackgroundNode == nil {
|
||||
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
|
||||
|
|
@ -214,40 +185,27 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||
|
||||
var insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1))
|
||||
|
||||
|
||||
var items: [ListViewItem] = []
|
||||
for messageItem in item.messageItems.reversed() {
|
||||
let authorPeerId = messageItem.peerId
|
||||
let replyAuthorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(10))
|
||||
let authorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))
|
||||
let botPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var messages = SimpleDictionary<MessageId, Message>()
|
||||
let messages = SimpleDictionary<MessageId, Message>()
|
||||
|
||||
peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: messageItem.author, lastName: "", username: nil, phone: nil, photo: messageItem.photo, botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
|
||||
|
||||
let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3)
|
||||
if let (replyAuthor, text, replyColor) = messageItem.reply {
|
||||
peers[replyAuthorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: replyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: replyColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
|
||||
messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[replyAuthorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
}
|
||||
|
||||
var media: [Media] = []
|
||||
if let (site, title, text) = messageItem.linkPreview, params.width > 320.0 {
|
||||
media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))))
|
||||
}
|
||||
peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
peers[botPeerId] = TelegramUser(id: botPeerId, accessHash: nil, firstName: messageItem.botAddress, lastName: "", username: messageItem.botAddress, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
|
||||
let media = messageItem.media
|
||||
var attributes: [MessageAttribute] = []
|
||||
if messageItem.reply != nil {
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false))
|
||||
if let entities = messageItem.entities {
|
||||
attributes.append(entities)
|
||||
}
|
||||
|
||||
attributes.append(InlineBotMessageAttribute(peerId: nil, title: "Test Attach"))
|
||||
attributes.append(InlineBotMessageAttribute(peerId: botPeerId, title: nil))
|
||||
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: authorPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
||||
}
|
||||
|
||||
|
|
@ -311,16 +269,6 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
|
||||
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId || currentItem.theme !== item.theme || currentItem.wallpaper != item.wallpaper {
|
||||
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
||||
strongSelf.view.addSubview(snapshot)
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.messageNodes = nodes
|
||||
var topOffset: CGFloat = 4.0
|
||||
for node in nodes {
|
||||
|
|
|
|||
|
|
@ -27,17 +27,20 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
let context: AccountContext
|
||||
let botName: String
|
||||
let botAddress: String
|
||||
let preparedMessage: PreparedInlineMessage
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
botAddress: String,
|
||||
preparedMessage: PreparedInlineMessage,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.botAddress = botAddress
|
||||
self.preparedMessage = preparedMessage
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
|
@ -101,7 +104,7 @@ private final class SheetContent: CombinedComponent {
|
|||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Test Attach mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("\(component.botName) mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
let amountFooter = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(amountInfoString),
|
||||
maximumNumberOfLines: 0,
|
||||
|
|
@ -120,17 +123,45 @@ private final class SheetContent: CombinedComponent {
|
|||
}
|
||||
}
|
||||
))
|
||||
|
||||
var text: String = ""
|
||||
var entities: TextEntitiesMessageAttribute?
|
||||
var media: [Media] = []
|
||||
|
||||
switch component.preparedMessage.result {
|
||||
case let .internalReference(reference):
|
||||
switch reference.message {
|
||||
case let .auto(textValue, entitiesValue, _):
|
||||
text = textValue
|
||||
entities = entitiesValue
|
||||
if let file = reference.file {
|
||||
media = [file]
|
||||
} else if let image = reference.image {
|
||||
media = [image]
|
||||
}
|
||||
case let .text(textValue, entitiesValue, disableUrlPreview, previewParameters, _):
|
||||
text = textValue
|
||||
entities = entitiesValue
|
||||
let _ = disableUrlPreview
|
||||
let _ = previewParameters
|
||||
case let .contact(contact, _):
|
||||
media = [contact]
|
||||
case let .mapLocation(map, _):
|
||||
media = [map]
|
||||
case let .invoice(invoice, _):
|
||||
media = [invoice]
|
||||
default:
|
||||
break
|
||||
}
|
||||
case let .externalReference(reference):
|
||||
let _ = reference
|
||||
}
|
||||
|
||||
let messageItem = PeerNameColorChatPreviewItem.MessageItem(
|
||||
outgoing: true,
|
||||
peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)),
|
||||
author: "",
|
||||
photo: [],
|
||||
nameColor: .blue,
|
||||
backgroundEmojiId: nil,
|
||||
reply: nil,
|
||||
linkPreview: nil,
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
text: text,
|
||||
entities: entities,
|
||||
media: media,
|
||||
botAddress: component.botAddress
|
||||
)
|
||||
|
||||
let listItemParams = ListViewItemLayoutParams(width: context.availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
||||
|
|
@ -197,7 +228,7 @@ private final class SheetContent: CombinedComponent {
|
|||
displaysProgress: false,
|
||||
action: {
|
||||
if let controller = controller() as? WebAppMessagePreviewScreen {
|
||||
let _ = controller
|
||||
controller.proceed()
|
||||
}
|
||||
}
|
||||
),
|
||||
|
|
@ -232,15 +263,18 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent {
|
|||
|
||||
private let context: AccountContext
|
||||
private let botName: String
|
||||
private let botAddress: String
|
||||
private let preparedMessage: PreparedInlineMessage
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
botAddress: String,
|
||||
preparedMessage: PreparedInlineMessage
|
||||
) {
|
||||
self.context = context
|
||||
self.botName = botName
|
||||
self.botAddress = botAddress
|
||||
self.preparedMessage = preparedMessage
|
||||
}
|
||||
|
||||
|
|
@ -265,6 +299,7 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent {
|
|||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
botName: context.component.botName,
|
||||
botAddress: context.component.botAddress,
|
||||
preparedMessage: context.component.preparedMessage,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
|
|
@ -290,12 +325,14 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent {
|
|||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
if let controller = controller() as? WebAppMessagePreviewScreen {
|
||||
controller.completeWithResult(false)
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
if let controller = controller() as? WebAppMessagePreviewScreen {
|
||||
controller.completeWithResult(false)
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -317,15 +354,18 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent {
|
|||
|
||||
public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let preparedMessage: PreparedInlineMessage
|
||||
fileprivate let completion: (Bool) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
botName: String,
|
||||
botAddress: String,
|
||||
preparedMessage: PreparedInlineMessage,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.preparedMessage = preparedMessage
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
|
|
@ -333,6 +373,7 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer
|
|||
component: WebAppMessagePreviewSheetComponent(
|
||||
context: context,
|
||||
botName: botName,
|
||||
botAddress: botAddress,
|
||||
preparedMessage: preparedMessage
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
|
|
@ -346,8 +387,43 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer
|
|||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
fileprivate func complete(peer: EnginePeer) {
|
||||
self.completeWithResult(true)
|
||||
self.dismiss()
|
||||
|
||||
let _ = self.context.engine.messages.enqueueOutgoingMessage(
|
||||
to: peer.id,
|
||||
replyTo: nil,
|
||||
storyId: nil,
|
||||
content: .preparedInlineMessage(preparedMessage)
|
||||
).start()
|
||||
}
|
||||
|
||||
private var completed = false
|
||||
fileprivate func completeWithResult(_ result: Bool) {
|
||||
guard !self.completed else {
|
||||
return
|
||||
}
|
||||
self.completion(result)
|
||||
}
|
||||
|
||||
fileprivate func proceed() {
|
||||
let requestPeerType = self.preparedMessage.peerTypes.requestPeerTypes
|
||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false))
|
||||
|
||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.complete(peer: peer)
|
||||
controller?.dismiss()
|
||||
}
|
||||
self.push(controller)
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
self.completeWithResult(false)
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue