Web app improvements

This commit is contained in:
Ilya Laktyushin 2022-03-28 18:34:52 +04:00
parent f0e03a2a4c
commit 11d9a8e760
14 changed files with 215 additions and 343 deletions

View File

@ -10,6 +10,21 @@ import MapKit
private let overflowInset: CGFloat = 0.0
public func attachmentDefaultTopInset(layout: ContainerViewLayout?) -> CGFloat {
guard let layout = layout else {
return 210.0
}
if case .compact = layout.metrics.widthClass {
var factor: CGFloat = 0.2488
if layout.size.width <= 320.0 {
factor = 0.15
}
return floor(max(layout.size.width, layout.size.height) * factor)
} else {
return 210.0
}
}
final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
let wrappingNode: ASDisplayNode
let clipNode: ASDisplayNode
@ -130,29 +145,14 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)?
private var defaultTopInset: CGFloat {
guard let (layout, _, _) = self.validLayout else{
return 210.0
}
if case .compact = layout.metrics.widthClass {
var factor: CGFloat = 0.2488
if layout.size.width <= 320.0 {
factor = 0.15
}
return floor(max(layout.size.width, layout.size.height) * factor)
} else {
return 210.0
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard let (layout, controllers, coveredByModalTransition) = self.validLayout else {
return
}
let isLandscape = layout.orientation == .landscape
let edgeTopInset = isLandscape ? 0.0 : defaultTopInset
let edgeTopInset = isLandscape ? 0.0 : attachmentDefaultTopInset(layout: layout)
switch recognizer.state {
case .began:
@ -349,6 +349,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
self.panGestureRecognizer?.isEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0)
let defaultTopInset = attachmentDefaultTopInset(layout: layout)
let isLandscape = layout.orientation == .landscape
let edgeTopInset = isLandscape ? 0.0 : defaultTopInset

View File

@ -71,7 +71,7 @@ private final class IconComponent: Component {
self.image = nil
}
_ = freeMediaFileInteractiveFetched(account: component.account, fileReference: .standalone(media: file)).start()
let _ = freeMediaFileInteractiveFetched(account: component.account, fileReference: .standalone(media: file)).start()
self.disposable = (svgIconImageFile(account: component.account, fileReference: .standalone(media: file), fetched: true)
|> runOn(Queue.concurrentDefaultQueue())
|> deliverOnMainQueue).start(next: { [weak self] transform in

View File

@ -161,6 +161,7 @@ public final class ShimmerEffectNode: ASDisplayNode {
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
case roundedRect(rect: CGRect, cornerRadius: CGFloat)
case rect(rect: CGRect)
case image(image: UIImage, rect: CGRect)
}
private let backgroundNode: ASDisplayNode
@ -231,6 +232,14 @@ public final class ShimmerEffectNode: ASDisplayNode {
UIGraphicsPopContext()
case let .rect(rect):
context.fill(rect)
case let .image(image, rect):
if let image = image.cgImage {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.clip(to: rect, mask: image)
context.fill(rect)
}
}
}
})

View File

@ -149,6 +149,7 @@ struct AccountMutableState {
var storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]
var displayAlerts: [(text: String, isDropAuth: Bool)] = []
var dismissBotWebViews: [Int64] = []
var insertedPeers: [PeerId: Peer] = [:]
@ -176,7 +177,7 @@ struct AccountMutableState {
self.updatedOutgoingUniqueMessageIds = [:]
}
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], branchOperationIndex: Int) {
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) {
self.initialState = initialState
self.operations = operations
self.state = state
@ -190,11 +191,12 @@ struct AccountMutableState {
self.namespacesWithHolesFromPreviousState = namespacesWithHolesFromPreviousState
self.updatedOutgoingUniqueMessageIds = updatedOutgoingUniqueMessageIds
self.displayAlerts = displayAlerts
self.dismissBotWebViews = dismissBotWebViews
self.branchOperationIndex = branchOperationIndex
}
func branch() -> AccountMutableState {
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count)
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count)
}
mutating func merge(_ other: AccountMutableState) {
@ -221,6 +223,7 @@ struct AccountMutableState {
}
self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs })
self.displayAlerts.append(contentsOf: other.displayAlerts)
self.dismissBotWebViews.append(contentsOf: other.dismissBotWebViews)
}
mutating func addPreCachedResource(_ resource: MediaResource, data: Data) {
@ -242,7 +245,11 @@ struct AccountMutableState {
mutating func addDisplayAlert(_ text: String, isDropAuth: Bool) {
self.displayAlerts.append((text: text, isDropAuth: isDropAuth))
}
mutating func addDismissWebView(_ queryId: Int64) {
self.dismissBotWebViews.append(queryId)
}
mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) {
self.addOperation(.DeleteMessagesWithGlobalIds(globalIds))
}
@ -501,6 +508,10 @@ struct AccountMutableState {
self.addOperation(.UpdateAttachMenuBots)
}
mutating func addDismissedWebView(queryId: Int64) {
self.addOperation(.UpdateAttachMenuBots)
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
@ -644,6 +655,7 @@ struct AccountFinalStateEvents {
let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, Bool)]
let displayAlerts: [(text: String, isDropAuth: Bool)]
let dismissBotWebViews: [Int64]
let delayNotificatonsUntil: Int32?
let updatedMaxMessageId: Int32?
let updatedQts: Int32?
@ -653,10 +665,10 @@ struct AccountFinalStateEvents {
let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id]
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: String, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: String, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds
@ -669,6 +681,7 @@ struct AccountFinalStateEvents {
self.updatedPeersNearby = updatedPeersNearby
self.isContactUpdates = isContactUpdates
self.displayAlerts = displayAlerts
self.dismissBotWebViews = dismissBotWebViews
self.delayNotificatonsUntil = delayNotificatonsUntil
self.updatedMaxMessageId = updatedMaxMessageId
self.updatedQts = updatedQts
@ -691,6 +704,7 @@ struct AccountFinalStateEvents {
self.updatedPeersNearby = state.updatedPeersNearby
self.isContactUpdates = state.isContactUpdates
self.displayAlerts = state.state.state.displayAlerts
self.dismissBotWebViews = state.state.state.dismissBotWebViews
self.delayNotificatonsUntil = state.delayNotificatonsUntil
self.updatedMaxMessageId = state.state.state.updatedMaxMessageId
self.updatedQts = state.state.state.updatedQts
@ -723,6 +737,6 @@ struct AccountFinalStateEvents {
let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId)
let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }))
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }))
}
}

View File

@ -1514,6 +1514,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate)
case .updateAttachMenuBots:
updatedState.addUpdateAttachMenuBots()
case let .updateWebViewResultSent(_, _, queryId):
updatedState.addDismissWebView(queryId)
default:
break
}

View File

@ -115,6 +115,11 @@ public final class AccountStateManager {
return self.displayAlertsPipe.signal()
}
private let dismissBotWebViewsPipe = ValuePipe<[Int64]>()
public var dismissBotWebViews: Signal<[Int64], NoError> {
return self.dismissBotWebViewsPipe.signal()
}
private let externallyUpdatedPeerIdsPipe = ValuePipe<[PeerId]>()
var externallyUpdatedPeerIds: Signal<[PeerId], NoError> {
return self.externallyUpdatedPeerIdsPipe.signal()
@ -760,6 +765,10 @@ public final class AccountStateManager {
self.displayAlertsPipe.putNext(events.displayAlerts)
}
if !events.dismissBotWebViews.isEmpty {
self.dismissBotWebViewsPipe.putNext(events.dismissBotWebViews)
}
if !events.externallyUpdatedPeerId.isEmpty {
self.externallyUpdatedPeerIdsPipe.putNext(Array(events.externallyUpdatedPeerId))
}

View File

@ -50,30 +50,49 @@ public enum RequestWebViewError {
case generic
}
private func keepWebView(network: Network, flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyToMessageId: MessageId?) -> Signal<Never, KeepWebViewError> {
let poll = Signal<Never, KeepWebViewError> { subscriber in
let signal: Signal<Never, KeepWebViewError> = network.request(Api.functions.messages.prolongWebView(flags: flags, peer: peer, bot: bot, queryId: queryId, replyToMsgId: replyToMessageId?.id))
|> mapError { _ -> KeepWebViewError in
return .generic
private func keepWebViewSignal(network: Network, stateManager: AccountStateManager, flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyToMessageId: MessageId?) -> Signal<Never, KeepWebViewError> {
let signal = Signal<Never, KeepWebViewError> { subscriber in
let poll = Signal<Never, KeepWebViewError> { subscriber in
let signal: Signal<Never, KeepWebViewError> = network.request(Api.functions.messages.prolongWebView(flags: flags, peer: peer, bot: bot, queryId: queryId, replyToMsgId: replyToMessageId?.id))
|> mapError { _ -> KeepWebViewError in
return .generic
}
|> ignoreValues
return signal.start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
}
|> ignoreValues
let keepAliveSignal = (
.complete()
|> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue())
|> then (poll)
)
|> restart
return signal.start(error: { error in
let pollDisposable = keepAliveSignal.start(error: { error in
subscriber.putError(error)
}, completed: {
})
let dismissDisposable = (stateManager.dismissBotWebViews
|> filter {
$0.contains(queryId)
}
|> take(1)).start(completed: {
subscriber.putCompletion()
})
let disposableSet = DisposableSet()
disposableSet.add(pollDisposable)
disposableSet.add(dismissDisposable)
return disposableSet
}
return (
.complete()
|> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue())
|> then (poll)
)
|> restart
return signal
}
func _internal_requestWebView(postbox: Postbox, network: Network, peerId: PeerId, botId: PeerId, url: String?, themeParams: [String: Any]?, replyToMessageId: MessageId?) -> Signal<RequestWebViewResult, RequestWebViewError> {
func _internal_requestWebView(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, botId: PeerId, url: String?, themeParams: [String: Any]?, replyToMessageId: MessageId?) -> Signal<RequestWebViewResult, RequestWebViewError> {
var serializedThemeParams: Api.DataJSON?
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
serializedThemeParams = .dataJSON(data: dataString)
@ -122,7 +141,7 @@ func _internal_requestWebView(postbox: Postbox, network: Network, peerId: PeerId
|> castError(RequestWebViewError.self)
|> switchToLatest
case let .webViewResultUrl(queryId, url):
return .single(.webViewResult(queryId: queryId, url: url, keepAliveSignal: keepWebView(network: network, flags: flags, peer: inputPeer, bot: inputBot, queryId: queryId, replyToMessageId: replyToMessageId)))
return .single(.webViewResult(queryId: queryId, url: url, keepAliveSignal: keepWebViewSignal(network: network, stateManager: stateManager, flags: flags, peer: inputPeer, bot: inputBot, queryId: queryId, replyToMessageId: replyToMessageId)))
}
}
}

View File

@ -323,7 +323,7 @@ public extension TelegramEngine {
}
public func requestWebView(peerId: PeerId, botId: PeerId, url: String?, themeParams: [String: Any]?, replyToMessageId: MessageId?) -> Signal<RequestWebViewResult, RequestWebViewError> {
return _internal_requestWebView(postbox: self.account.postbox, network: self.account.network, peerId: peerId, botId: botId, url: url, themeParams: themeParams, replyToMessageId: replyToMessageId)
return _internal_requestWebView(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, botId: botId, url: url, themeParams: themeParams, replyToMessageId: replyToMessageId)
}
public func requestSimpleWebView(botId: PeerId, url: String, themeParams: [String: Any]?) -> Signal<String, RequestSimpleWebViewError> {

View File

@ -3359,7 +3359,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else {
return
}
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil)
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil, replyToMessageId: nil, iconFile: nil)
controller.getNavigationController = { [weak self] in
return self?.effectiveNavigationController
}
controller.navigationPresentation = .modal
strongSelf.push(controller)
}, error: { [weak self] error in
@ -3379,7 +3382,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
switch result {
case let .webViewResult(queryId, url, keepAliveSignal):
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: queryId, buttonText: buttonText, keepAliveSignal: keepAliveSignal)
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: queryId, buttonText: buttonText, keepAliveSignal: keepAliveSignal, replyToMessageId: nil, iconFile: nil)
controller.getNavigationController = { [weak self] in
return self?.effectiveNavigationController
}
controller.navigationPresentation = .modal
strongSelf.push(controller)
case .requestConfirmation:
@ -10828,8 +10834,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = strongSelf.configurePollCreation()
completion(controller, nil)
strongSelf.controllerNavigationDisposable.set(nil)
case let .app(botId, botName, _):
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil, buttonText: nil, keepAliveSignal: nil)
case let .app(botId, botName, botIcon):
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil, buttonText: nil, keepAliveSignal: nil, replyToMessageId: replyMessageId, iconFile: botIcon)
controller.getNavigationController = { [weak self] in
return self?.effectiveNavigationController
}
completion(controller, nil)
strongSelf.controllerNavigationDisposable.set(nil)
}

View File

@ -3160,16 +3160,24 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
botAddressName = attribute.title
}
return .optionalAction({
if let botAddressName = botAddressName {
item.controllerInteraction.updateInputState { textInputState in
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
}
item.controllerInteraction.updateInputMode { _ in
return .text
}
if let peerId = attribute.peerId {
if let botPeer = item.message.peers[peerId] as? TelegramUser, let inlinePlaceholder = botPeer.botInfo?.inlinePlaceholder, !inlinePlaceholder.isEmpty {
return .optionalAction({
if let botAddressName = botAddressName {
item.controllerInteraction.updateInputState { textInputState in
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
}
item.controllerInteraction.updateInputMode { _ in
return .text
}
}
})
} else {
return .optionalAction({
item.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, item.message.peers[peerId])
})
}
})
}
}
}
}

View File

@ -21,6 +21,7 @@ swift_library(
"//submodules/CounterContollerTitleView:CounterContollerTitleView",
"//submodules/HexColor:HexColor",
"//submodules/PhotoResources:PhotoResources",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -83,6 +83,7 @@ private final class WebAppAlertContentNode: AlertContentNode {
self.updateTheme(theme)
let _ = freeMediaFileInteractiveFetched(account: account, fileReference: .standalone(media: peerIcon)).start()
self.iconDisposable = (svgIconImageFile(account: account, fileReference: .standalone(media: peerIcon))
|> deliverOnMainQueue).start(next: { [weak self] transform in
if let strongSelf = self {

View File

@ -13,6 +13,8 @@ import CounterContollerTitleView
import ContextUI
import PresentationDataUtils
import HexColor
import ShimmerEffect
import PhotoResources
private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler {
private let f: (WKScriptMessage) -> ()
@ -52,13 +54,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
private class Node: ViewControllerTracingNode, WKNavigationDelegate, UIScrollViewDelegate {
private weak var controller: WebAppController?
private var webView: WKWebView?
fileprivate var webView: WKWebView?
private var placeholderIcon: UIImage?
private var placeholderNode: ShimmerEffectNode?
private let context: AccountContext
var presentationData: PresentationData
private let present: (ViewController, Any?) -> Void
private var queryId: Int64?
private var iconDisposable: Disposable?
private var keepAliveDisposable: Disposable?
init(context: AccountContext, controller: WebAppController, present: @escaping (ViewController, Any?) -> Void) {
@ -129,6 +135,27 @@ public final class WebAppController: ViewController, AttachmentContainable {
webView.scrollView.delegate = self
self.webView = webView
let placeholderNode = ShimmerEffectNode()
self.addSubnode(placeholderNode)
self.placeholderNode = placeholderNode
if let iconFile = controller.iconFile {
let _ = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: iconFile)).start()
self.iconDisposable = (svgIconImageFile(account: self.context.account, fileReference: .standalone(media: iconFile))
|> deliverOnMainQueue).start(next: { [weak self] transform in
if let strongSelf = self {
let imageSize = CGSize(width: 75.0, height: 75.0)
let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())
let drawingContext = transform(arguments)
if let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) {
strongSelf.placeholderIcon = image
strongSelf.updatePlaceholder()
}
}
})
}
if let url = controller.url, let queryId = controller.queryId, let keepAliveSignal = controller.keepAliveSignal {
self.queryId = queryId
if let parsedUrl = URL(string: url) {
@ -140,9 +167,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let strongSelf = self {
strongSelf.controller?.dismiss()
}
}, completed: { [weak self] in
if let strongSelf = self {
strongSelf.controller?.dismiss()
}
})
} else {
let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, themeParams: generateWebAppThemeParams(presentationData.theme), replyToMessageId: nil)
let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, themeParams: generateWebAppThemeParams(presentationData.theme), replyToMessageId: controller.replyToMessageId)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
@ -158,6 +189,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let strongSelf = self {
strongSelf.controller?.dismiss()
}
}, completed: { [weak self] in
if let strongSelf = self {
strongSelf.controller?.dismiss()
}
})
}
case .requestConfirmation:
@ -168,6 +203,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
deinit {
self.iconDisposable?.dispose()
self.keepAliveDisposable?.dispose()
}
@ -191,6 +227,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
}
private func updatePlaceholder() {
guard let image = self.placeholderIcon else {
return
}
let theme = self.presentationData.theme
self.placeholderNode?.update(backgroundColor: self.backgroundColor ?? .clear, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.image(image: image, rect: CGRect(origin: CGPoint(), size: image.size))], horizontal: true, size: image.size)
}
private var loadCount = 0
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.loadCount += 1
@ -201,7 +245,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
Queue.mainQueue().after(0.1, {
if self.loadCount == 0, let webView = self.webView {
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear).updateAlpha(layer: webView.layer, alpha: 1.0)
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
transition.updateAlpha(layer: webView.layer, alpha: 1.0)
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
transition.updateAlpha(node: placeholderNode, alpha: 0.0, completion: { [weak placeholderNode] _ in
placeholderNode?.removeFromSupernode()
})
}
}
})
}
@ -215,6 +266,21 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let webView = self.webView {
webView.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom)))
}
if let placeholderNode = self.placeholderNode {
let iconSize = CGSize(width: 75.0, height: 75.0)
let height: CGFloat
if case .compact = layout.metrics.widthClass {
height = layout.size.height - attachmentDefaultTopInset(layout: layout) - 56.0
} else {
height = layout.size.height - 56.0
}
let placeholderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - iconSize.width) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
transition.updateFrame(node: placeholderNode, frame: placeholderFrame)
placeholderNode.updateAbsoluteRect(placeholderFrame, within: layout.size)
}
}
func animateIn() {
@ -263,6 +329,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
resultString = convertedString
}
if let resultString = resultString {
self.dismissed = true
let _ = (self.context.engine.messages.sendWebViewData(botId: controller.botId, buttonText: buttonText, data: resultString)).start()
}
}
@ -315,12 +382,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
private let queryId: Int64?
private let buttonText: String?
private let keepAliveSignal: Signal<Never, KeepWebViewError>?
private let replyToMessageId: MessageId?
private let iconFile: TelegramMediaFile?
private var presentationData: PresentationData
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, botId: PeerId, botName: String, url: String?, queryId: Int64?, buttonText: String?, keepAliveSignal: Signal<Never, KeepWebViewError>?) {
public var getNavigationController: () -> NavigationController? = { return nil }
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, botId: PeerId, botName: String, url: String?, queryId: Int64?, buttonText: String?, keepAliveSignal: Signal<Never, KeepWebViewError>?, replyToMessageId: MessageId?, iconFile: TelegramMediaFile?) {
self.context = context
self.peerId = peerId
self.botId = botId
@ -328,6 +399,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.queryId = queryId
self.buttonText = buttonText
self.keepAliveSignal = keepAliveSignal
self.replyToMessageId = replyToMessageId
self.iconFile = iconFile
self.updatedPresentationData = updatedPresentationData
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
@ -408,26 +481,27 @@ public final class WebAppController: ViewController, AttachmentContainable {
let botId = self.botId
let items = context.engine.messages.attachMenuBots()
|> map { attachMenuBots -> ContextController.Items in
|> map { [weak self] attachMenuBots -> ContextController.Items in
var items: [ContextMenuItem] = []
if peerId != botId {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_OpenBot, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
}, action: { [weak self] _, f in
f(.default)
// if let strongSelf = self {
// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf., context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId)))
// }
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
strongSelf.dismiss()
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: strongSelf.botId)))
}
})))
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebApp_ReloadPage, icon: { theme in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_ReloadPage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
}, action: { [weak self] _, f in
f(.default)
self?.controllerNode.webView?.reload()
})))
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == botId}) {

View File

@ -1,276 +0,0 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import PhotoResources
private final class WebAppPreviewContentNode: AlertContentNode {
private let context: AccountContext
private let presentationData: PresentationData
private let result: ChatContextResult
private let outgoingMessage: EnqueueMessage?
private var previewItem: ListViewItem?
private var previewNode: ListViewItemNode?
private let titleNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
private var iconDisposable: Disposable?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(context: AccountContext, theme: AlertControllerTheme, presentationData: PresentationData, to peerId: PeerId, botId: PeerId, result: ChatContextResult, actions: [TextAlertAction]) {
self.context = context
self.presentationData = presentationData
self.result = result
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 0
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
self.outgoingMessage = self.context.engine.messages.outgoingMessageWithChatContextResult(to: peerId, botId: botId, result: result, replyToMessageId: nil, hideVia: true, silentPosting: false, scheduleTime: nil, correlationId: nil)
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
if let outgoingMessage = self.outgoingMessage, case let .message(text, attributes, mediaReference, _, _, _) = outgoingMessage {
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
var peers = SimpleDictionary<PeerId, Peer>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
var media: [Media] = []
if let mediaReference = mediaReference {
media.append(mediaReference.media)
}
let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: scheduleWhenOnlineTimestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: attributes, media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
let previewItem = context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: presentationData.theme.withUpdated(preview: true), strings: presentationData.strings, wallpaper: .color(0xffffff), fontSize: presentationData.chatFontSize, chatBubbleCorners: presentationData.chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: nil, availableReactions: nil, isCentered: true)
self.previewItem = previewItem
}
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.WebApp_MessagePreview, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
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 , 290.0)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let textSize = self.titleNode.measure(size)
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)
origin.y += textSize.height + 12.0
var iconSize = CGSize()
var iconFrame = CGRect()
let sideInset: CGFloat = 0.0
let params = ListViewItemLayoutParams(width: size.width, leftInset: sideInset, rightInset: sideInset, availableHeight: size.height)
if let previewItem = self.previewItem {
if let previewNode = self.previewNode {
previewItem.updateNode(async: { $0() }, node: {
return previewNode
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: layout.size.height))
previewNode.contentSize = layout.contentSize
previewNode.insets = layout.insets
previewNode.frame = nodeFrame
previewNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
} else {
var itemNode: ListViewItemNode?
previewItem.nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
itemNode!.isUserInteractionEnabled = false
self.addSubnode(itemNode!)
self.previewNode = itemNode
}
iconSize = CGSize(width: 0.0, height: self.previewNode?.frame.height ?? 0.0)
self.previewNode?.frame = CGRect(x: 4.0, y: origin.y, width: size.width, height: iconSize.height)
origin.y += iconSize.height
}
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, 260.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) + 19.0
textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0)
transition.updateFrame(node: self.titleNode, frame: textFrame)
return resultSize
}
}
public func webAppPreviewResultController(context: AccountContext, to peerId: PeerId, botId: PeerId, result: ChatContextResult, completion: @escaping () -> Void) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissImpl: ((Bool) -> Void)?
var contentNode: WebAppPreviewContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_Send, action: {
dismissImpl?(true)
completion()
})]
contentNode = WebAppPreviewContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationData: presentationData, to: peerId, botId: botId, result: result, 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
}