chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
+35 -14
View File
@@ -1,5 +1,5 @@
import UIKit
import SwiftSignalKit
@preconcurrency import SwiftSignalKit
import Display
import TelegramCore
import UserNotifications
@@ -41,7 +41,8 @@ import MediaEditor
import TelegramUIDeclareEncodables
import ContextMenuScreen
import MetalEngine
import RecaptchaEnterprise
import RecaptchaEnterpriseSDK
import NavigationBarImpl
#if canImport(AppCenter)
import AppCenter
@@ -330,6 +331,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let launchStartTime = CFAbsoluteTimeGetCurrent()
defaultNavigationBarImpl = { presentationData in
return NavigationBarImpl(presentationData: presentationData)
}
let (window, hostView) = nativeWindowHostView()
let statusBarHost = ApplicationStatusBarHost(scene: window.windowScene)
self.mainWindow = Window1(hostView: hostView, statusBarHost: statusBarHost)
@@ -2417,15 +2422,25 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
if let proxyData = parseProxyUrl(sharedContext: sharedContext, url: url) {
authContext.rootController.view.endEditing(true)
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
let controller = ProxyServerActionSheetController(presentationData: presentationData, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, updatedPresentationData: nil)
let controller = ProxyServerActionSheetController(sharedContext: authContext.sharedContext, presentationData: presentationData, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, updatedPresentationData: nil)
authContext.rootController.currentWindow?.present(controller, on: PresentationSurfaceLevel.root, blockInteraction: false, completion: {})
} else if let secureIdData = parseSecureIdUrl(url) {
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
authContext.rootController.currentWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Passport_NotLoggedInMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Calls_NotNow, action: {
if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) {
UIApplication.shared.open(callbackUrl, options: [:], completionHandler: nil)
}
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), on: .root, blockInteraction: false, completion: {})
let alertController = textAlertController(
sharedContext: authContext.sharedContext,
title: nil,
text: presentationData.strings.Passport_NotLoggedInMessage,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Calls_NotNow, action: {
if let callbackUrl = URL(string: secureIdCallbackUrl(with: secureIdData.callbackUrl, peerId: secureIdData.peerId, result: .cancel, parameters: [:])) {
UIApplication.shared.open(callbackUrl, options: [:], completionHandler: nil)
}
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
)
authContext.rootController.currentWindow?.present(alertController, on: .root, blockInteraction: false, completion: {})
}
}
})
@@ -2960,12 +2975,18 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
if currentVersion < version {
let presentationData = sharedContext.sharedContext.currentPresentationData.with { $0 }
sharedContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "A new build is available", actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: "Show", action: {
sharedContext.sharedContext.applicationBindings.openUrl(releaseNotesUrl)
})
]), on: .root, blockInteraction: false, completion: {})
let alertController = textAlertController(
sharedContext: sharedContext.sharedContext,
title: "A new build is available",
text: "",
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: "Show", action: {
sharedContext.sharedContext.applicationBindings.openUrl(releaseNotesUrl)
})
]
)
sharedContext.sharedContext.mainWindow?.present(alertController, on: .root, blockInteraction: false, completion: {})
}
}))
})
@@ -499,7 +499,7 @@ final class AuthorizedApplicationContext {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var acceptImpl: ((String?) -> Void)?
var declineImpl: (() -> Void)?
let controller = TermsOfServiceController(presentationData: presentationData, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in
let controller = TermsOfServiceController(context: strongSelf.context, presentationData: presentationData, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in
acceptImpl?(proccedBot)
}, decline: {
declineImpl?()
@@ -124,6 +124,9 @@ import AdsInfoScreen
import PostSuggestionsSettingsScreen
import ChatSendStarsScreen
import ChatSendAsContextMenu
import GlobalControlPanelsContext
import ComponentFlow
import ComponentDisplayAdapters
extension ChatControllerImpl {
func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, historyNode: ChatHistoryListNodeImpl, apply: @escaping ((ContainedViewLayoutTransition?) -> Void) -> Void) {
@@ -228,17 +231,6 @@ extension ChatControllerImpl {
}
self.navigationBar?.userInfo = contentData.state.navigationUserInfo
if self.chatTitleView?.titleContent != contentData.state.chatTitleContent {
var animateTitleContents = false
if !synchronous, case let .messageOptions(_, _, info) = self.subject, case .reply = info {
animateTitleContents = true
}
if animateTitleContents && self.chatTitleView?.titleContent != nil {
self.chatTitleView?.animateLayoutTransition()
}
self.chatTitleView?.titleContent = contentData.state.chatTitleContent
}
if let infoAvatar = contentData.state.infoAvatar {
switch infoAvatar {
case let .peer(peer, imageOverride, contextActionIsEnabled, accessibilityLabel):
@@ -381,6 +373,13 @@ extension ChatControllerImpl {
if previousState.pinnedMessage != contentData.state.pinnedMessage {
animated = true
}
if previousState.translationState?.isEnabled != contentData.state.translationState?.isEnabled {
animated = true
}
if previousState.chatTitleContent != contentData.state.chatTitleContent {
animated = true
}
var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
if let forceAnimationTransition {
transition = forceAnimationTransition
@@ -389,6 +388,22 @@ extension ChatControllerImpl {
transition = .immediate
}
if let chatTitleContent = contentData.state.chatTitleContent {
var titleTransition = ComponentTransition(transition)
if case .messageOptions = self.subject {
titleTransition = titleTransition.withAnimation(.none)
}
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
content: chatTitleContent,
transition: titleTransition
)
}
self.updateChatPresentationInterfaceState(transition: transition, interactive: false, { presentationInterfaceState in
var presentationInterfaceState = presentationInterfaceState
presentationInterfaceState = presentationInterfaceState.updatedPeer({ _ in
@@ -677,7 +692,7 @@ extension ChatControllerImpl {
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self)
if let currentItem = self.tempVoicePlaylistCurrentItem {
if let currentItem = self.globalControlPanelsContext?.tempVoicePlaylistCurrentItem {
self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem)
}
@@ -988,7 +1003,15 @@ extension ChatControllerImpl {
}
if let errorText = errorText {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: errorText,
actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
)
strongSelf.present(alertController, in: .window(.root))
return
}
}
@@ -1092,10 +1115,15 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
case let .businessLinkSetup(link):
if messages.count > 1 {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText,
actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
)
strongSelf.present(alertController, in: .window(.root))
return
}
@@ -1871,8 +1899,9 @@ extension ChatControllerImpl {
titleString = self.presentationData.strings.Chat_DeletePaidMessageTon_Title
textString = self.presentationData.strings.Chat_DeletePaidMessageTon_Text
}
self.present(standardTextAlertController(
theme: AlertControllerTheme(presentationData: self.presentationData),
let alertController = textAlertController(
context: self.context,
title: titleString,
text: textString,
actions: [
@@ -1884,9 +1913,9 @@ extension ChatControllerImpl {
}),
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical,
parseMarkdown: true
), in: .window(.root))
actionLayout: .vertical
)
self.present(alertController, in: .window(.root))
}
if let contextController {
contextController.dismiss(completion: commit)
@@ -3638,7 +3667,7 @@ extension ChatControllerImpl {
}
}
let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, account: strongSelf.context.account, text: text?.string ?? "", link: link, allowEmpty: true, apply: { [weak self] link in
let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: text?.string ?? "", link: link, apply: { [weak self] link in
if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange {
if let link {
if !link.isEmpty {
@@ -4590,6 +4619,44 @@ extension ChatControllerImpl {
})
}
var mediaPlayback = false
var liveLocationMode: GlobalControlPanelsContext.LiveLocationMode?
if case .standard = self.mode {
//mediaAccessoryPanelVisibility = .specific(size: .compact)
mediaPlayback = true
liveLocationMode = self.chatLocation.peerId.flatMap(GlobalControlPanelsContext.LiveLocationMode.peer)
}
var groupCallPanelSource: EnginePeer.Id?
switch self.chatLocation {
case let .peer(peerId):
switch self.subject {
case .message, .none:
groupCallPanelSource = peerId
default:
break
}
case .replyThread, .customChatContents:
break
}
let globalControlPanelsContext = GlobalControlPanelsContext(
context: self.context,
mediaPlayback: mediaPlayback,
liveLocationMode: liveLocationMode,
groupCalls: groupCallPanelSource,
chatListNotices: false
)
self.globalControlPanelsContext = globalControlPanelsContext
self.globalControlPanelsContextStateDisposable = (globalControlPanelsContext.state
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
guard let self else {
return
}
self.globalControlPanelsContextState = state
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
})
self.displayNodeDidLoad()
}
@@ -4745,7 +4812,18 @@ extension ChatControllerImpl {
return true
}
})
strongSelf.chatTitleView?.inputActivities = (peerId, displayActivities)
strongSelf.chatTitleView?.updateActivities(
activities: ChatTitleComponent.Activities(
peerId: peerId,
items: displayActivities.map { item -> ChatTitleComponent.Activities.Item in
return ChatTitleComponent.Activities.Item(
peer: EnginePeer(item.0),
activity: item.1
)
}
),
transition: .spring(duration: 0.4)
)
strongSelf.peerInputActivitiesPromise.set(.single(activities))
@@ -361,49 +361,54 @@ extension ChatControllerImpl {
strongSelf.recorderDataDisposable.set(nil)
} else {
let randomId = Int64.random(in: Int64.min ... Int64.max)
let resource = LocalFileMediaResource(fileId: randomId)
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData)
let originalData = data.compressedData
let duration = data.duration
let waveformBuffer: Data? = data.waveform
let correlationId = Int64.random(in: 0 ..< Int64.max)
var usedCorrelationId = false
// GHOSTGRAM: Check if Voice Morpher needs processing
let needsProcessing = VoiceMorpherManager.shared.isEnabled && VoiceMorpherManager.shared.selectedPreset != .disabled
var shouldAnimateMessageTransition = strongSelf.chatDisplayNode.shouldAnimateMessageTransition
if strongSelf.chatLocation.threadId == nil, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
shouldAnimateMessageTransition = false
}
if shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton {
usedCorrelationId = true
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: {
guard let strongSelf = self else {
return
if needsProcessing {
// Show processing indicator
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil))
statusController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style
strongSelf.present(statusController, in: .window(.root))
// Process async
VoiceMorpherEngine.shared.processOggData(originalData) { [weak self, weak statusController] result in
DispatchQueue.main.async {
statusController?.dismiss()
guard let strongSelf = self else { return }
let processedData: Data
switch result {
case .success(let data):
processedData = data
case .failure:
processedData = originalData // fallback to original on error
}
strongSelf.finishSendingVoiceMessage(
randomId: randomId,
processedData: processedData,
duration: duration,
waveformBuffer: waveformBuffer,
viewOnce: viewOnce
)
}
strongSelf.audioRecorder.set(.single(nil))
})
} else {
strongSelf.audioRecorder.set(.single(nil))
}
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }
})
}
}, usedCorrelationId ? correlationId : nil)
var attributes: [MessageAttribute] = []
if viewOnce {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil))
} else {
// No processing needed, send directly
strongSelf.finishSendingVoiceMessage(
randomId: randomId,
processedData: originalData,
duration: duration,
waveformBuffer: waveformBuffer,
viewOnce: viewOnce
)
}
strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])])
strongSelf.recorderFeedback?.tap()
strongSelf.recorderFeedback = nil
strongSelf.recorderDataDisposable.set(nil)
@@ -774,4 +779,50 @@ extension ChatControllerImpl {
self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect)
}
}
// MARK: - GHOSTGRAM: Voice Morpher Helper
private func finishSendingVoiceMessage(
randomId: Int64,
processedData: Data,
duration: Double,
waveformBuffer: Data?,
viewOnce: Bool
) {
let resource = LocalFileMediaResource(fileId: randomId)
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: processedData)
let correlationId = Int64.random(in: 0 ..< Int64.max)
var usedCorrelationId = false
var shouldAnimateMessageTransition = self.chatDisplayNode.shouldAnimateMessageTransition
if self.chatLocation.threadId == nil, let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
shouldAnimateMessageTransition = false
}
if shouldAnimateMessageTransition, let textInputPanelNode = self.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton {
usedCorrelationId = true
self.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { [weak self] in
self?.audioRecorder.set(.single(nil))
})
} else {
self.audioRecorder.set(.single(nil))
}
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }
})
}
}, usedCorrelationId ? correlationId : nil)
var attributes: [MessageAttribute] = []
if viewOnce {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil))
}
self.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(processedData.count), attributes: [.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])])
}
}
@@ -280,13 +280,19 @@ extension ChatControllerImpl {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.scheduledMessages)
})
], parseMarkdown: true), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle,
text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.scheduledMessages)
})
]
)
strongSelf.present(alertController, in: .window(.root))
}))
} else {
if let _ = canClearForMyself ?? canClearForEveryone {
@@ -310,13 +316,19 @@ extension ChatControllerImpl {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: confirmationText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: {
beginClear(.forEveryone)
})
], parseMarkdown: true), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle,
text: confirmationText,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: {
beginClear(.forEveryone)
})
]
)
strongSelf.present(alertController, in: .window(.root))
}))
}
if let canClearForMyself = canClearForMyself {
@@ -330,13 +342,19 @@ extension ChatControllerImpl {
items.append(ActionSheetButtonItem(title: text, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if mainPeer.id == context.account.peerId, let strongSelf = self {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.forLocalPeer)
})
], parseMarkdown: true), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle,
text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.forLocalPeer)
})
]
)
strongSelf.present(alertController, in: .window(.root))
} else {
beginClear(.forLocalPeer)
}
@@ -17,6 +17,7 @@ import PremiumUI
import TooltipUI
import TopMessageReactions
import TelegramNotices
import PresentationDataUtils
extension ChatControllerImpl {
func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void {
@@ -433,9 +434,15 @@ extension ChatControllerImpl {
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
)
strongSelf.present(alertController, in: .window(.root))
}
return
}
@@ -16,7 +16,12 @@ extension ChatControllerImpl {
break
}
}
let controller = factCheckAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, value: currentText, entities: currentEntities, apply: { [weak self] text, entities in
let controller = factCheckAlertController(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
value: currentText,
entities: currentEntities,
apply: { [weak self] text, entities in
guard let self else {
return
}
@@ -357,7 +357,9 @@ extension ChatControllerImpl {
})))
}
items.append(.separator)
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] action in
@@ -14,6 +14,7 @@ import PresentationDataUtils
import TelegramCallsUI
import AttachmentUI
import WebUI
import LegacyChatHeaderPanelComponent
func updateChatPresentationInterfaceStateImpl(
selfController: ChatControllerImpl,
@@ -14,6 +14,7 @@ import TelegramCore
import SwiftSignalKit
import UndoUI
import ShareController
import LegacyChatHeaderPanelComponent
private final class ChatBusinessLinkTitlePanelComponent: Component {
let context: AccountContext
+120 -164
View File
@@ -144,6 +144,9 @@ import FaceScanScreen
import ChatThemeScreen
import ChatTextInputPanelNode
import ChatInputAccessoryPanel
import GlobalControlPanelsContext
import ChatSearchNavigationContentNode
import ChatAgeRestrictionAlertController
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@@ -293,7 +296,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let chatThemePromise = Promise<ChatTheme?>()
let chatWallpaperPromise = Promise<TelegramWallpaper?>()
var chatTitleView: ChatTitleView?
var chatTitleView: ChatNavigationBarTitleView?
var leftNavigationButton: ChatNavigationButton?
var rightNavigationButton: ChatNavigationButton?
var secondaryRightNavigationButton: ChatNavigationButton?
@@ -524,9 +527,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let joinChannelDisposable = MetaDisposable()
var shouldDisplayDownButton = false
var hasEmbeddedTitleContent = false
var isEmbeddedTitleContentHidden = false
var chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
@@ -626,6 +626,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var lastPostedScheduledMessagesToastTimestamp: Double = 0.0
var postedScheduledMessagesEventsDisposable: Disposable?
var globalControlPanelsContext: GlobalControlPanelsContext?
var globalControlPanelsContextState: GlobalControlPanelsContext.State?
var globalControlPanelsContextStateDisposable: Disposable?
public init(
context: AccountContext,
chatLocation: ChatLocation,
@@ -675,26 +679,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase)
self.wallpaperReady.set(self.chatBackgroundNode.isReady)
var locationBroadcastPanelSource: LocationBroadcastPanelSource
var groupCallPanelSource: GroupCallPanelSource
switch chatLocation {
case let .peer(peerId):
locationBroadcastPanelSource = .peer(peerId)
switch subject {
case .message, .none:
groupCallPanelSource = .peer(peerId)
default:
groupCallPanelSource = .none
}
case .replyThread:
locationBroadcastPanelSource = .none
groupCallPanelSource = .none
case .customChatContents:
locationBroadcastPanelSource = .none
groupCallPanelSource = .none
}
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let forcedTheme = self.forcedTheme {
presentationData = presentationData.withUpdated(theme: forcedTheme)
@@ -707,7 +691,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.stickerSettings = ChatInterfaceStickerSettings()
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
if case let .customChatContents(customChatContents) = subject {
switch customChatContents.kind {
@@ -726,25 +710,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.presentationInterfaceStatePromise = ValuePromise(self.presentationInterfaceState)
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
if case .standard = mode {
mediaAccessoryPanelVisibility = .specific(size: .compact)
} else {
locationBroadcastPanelSource = .none
groupCallPanelSource = .none
}
let navigationBarPresentationData: NavigationBarPresentationData?
switch mode {
case .inline, .standard(.embedded):
navigationBarPresentationData = nil
default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: false, hideBadge: false, style: .glass)
}
self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor)
self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.chat.inputPanel.panelControlColor)
self.moreBarButton.isUserInteractionEnabled = true
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource)
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData)
self._hasGlassStyle = true
self.automaticallyControlPresentationContextLayout = false
self.blocksBackgroundWhenInOverlay = true
@@ -754,6 +733,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.ready.set(.never())
self.chatBackgroundNode.isDarkUpdated = { [weak self] in
guard let self else {
return
}
self.updateStatusBarPresentation(animated: false)
}
self.scrollToTop = { [weak self] in
guard let strongSelf = self, strongSelf.isNodeLoaded else {
return
@@ -800,12 +786,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
textString = strongSelf.presentationData.strings.QuickReply_ChatRemoveAwayMessage_Text
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: titleString, text: textString, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_DeleteAction, action: { [weak strongSelf] in
strongSelf?.dismiss()
})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: titleString,
text: textString,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_DeleteAction, action: { [weak strongSelf] in
strongSelf?.dismiss()
})
]
)
strongSelf.present(alertController, in: .window(.root))
return false
}
@@ -817,12 +809,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let message = inputText.string
if message != link.message || entities != link.entities {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Business_Links_AlertUnsavedText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Business_Links_AlertUnsavedAction, action: { [weak strongSelf] in
strongSelf?.dismiss()
})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: strongSelf.presentationData.strings.Business_Links_AlertUnsavedText,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Business_Links_AlertUnsavedAction, action: { [weak strongSelf] in
strongSelf?.dismiss()
})
]
)
strongSelf.present(alertController, in: .window(.root))
return false
}
@@ -1833,9 +1831,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string,
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
)
strongSelf.present(alertController, in: .window(.root))
}
return
}
@@ -2448,6 +2452,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let controller = self.context.sharedContext.makeGiftOfferScreen(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
gift: gift,
peer: peer,
amount: amount,
@@ -2477,7 +2482,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if message.effectivelyIncoming(strongSelf.context.account.peerId) {
switch buttonType {
case 0:
let promptController = promptController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, text: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Title, titleFont: .bold, value: "", placeholder: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Placeholder, characterLimit: 4096, apply: { value in
let promptController = promptController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Title, titleFont: .bold, value: "", placeholder: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Placeholder, characterLimit: 4096, apply: { value in
guard let self else {
return
}
@@ -2501,13 +2506,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille))
}
#if DEBUG
if "".isEmpty {
funds = nil
}
#endif
var isAdmin = false
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
isAdmin = true
@@ -4323,7 +4322,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var message: Message?
if let historyMessage = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
message = historyMessage
} else if let panelMessage = self.chatDisplayNode.adPanelNode?.message, panelMessage.id == messageId {
} else if let panelMessage = self.chatDisplayNode.adPanelMessage, panelMessage.id == messageId {
message = panelMessage
}
@@ -4585,7 +4584,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
adOpaqueId = adAttribute.opaqueId
}
}
if adOpaqueId == nil, let panelMessage = self.chatDisplayNode.adPanelNode?.message, let adAttribute = panelMessage.adAttribute {
if adOpaqueId == nil, let panelMessage = self.chatDisplayNode.adPanelMessage, let adAttribute = panelMessage.adAttribute {
adOpaqueId = adAttribute.opaqueId
}
let _ = self.context.engine.accountData.updateAdMessagesEnabled(enabled: false).start()
@@ -5177,14 +5176,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
}
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
if case .messageOptions = self.subject {
self.chatTitleView?.disableAnimations = true
}
self.chatTitleView = ChatNavigationBarTitleView(frame: CGRect())
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.longPressed = { [weak self] in
self.chatTitleView?.longTapAction = { [weak self] in
if let strongSelf = self, let peerView = strongSelf.contentData?.state.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
if case .standard(.previewing) = strongSelf.mode {
} else {
@@ -5570,7 +5565,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.pressed = { [weak self] in
self.chatTitleView?.tapAction = { [weak self] in
self?.navigationButtonAction(.openChatInfo(expandAvatar: false, section: nil))
}
@@ -6136,7 +6131,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.networkStateDisposable = (context.account.networkState |> deliverOnMainQueue).startStrict(next: { [weak self] state in
if let strongSelf = self, case .standard(.default) = strongSelf.presentationInterfaceState.mode {
strongSelf.chatTitleView?.networkState = state
strongSelf.chatTitleView?.updateNetworkState(networkState: state, transition: .spring(duration: 0.4))
}
})
@@ -6241,6 +6236,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.newTopicEventsDisposable?.dispose()
self.updateMessageTodoDisposables?.dispose()
self.preloadNextChatPeerIdDisposable.dispose()
self.globalControlPanelsContextStateDisposable?.dispose()
}
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
@@ -6324,7 +6320,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .embedded:
self.statusBar.statusBarStyle = .Ignore
default:
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
if let isDark = self.chatDisplayNode.backgroundNode.isDark {
if isDark {
self.statusBar.statusBarStyle = .White
} else {
self.statusBar.statusBarStyle = .Black
}
} else {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
}
self.deferScreenEdgeGestures = []
}
case .overlay:
@@ -6360,18 +6364,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let presentationTheme: PresentationTheme
if let forcedNavigationBarTheme = self.forcedNavigationBarTheme {
presentationTheme = forcedNavigationBarTheme
navigationBarTheme = NavigationBarTheme(rootControllerTheme: forcedNavigationBarTheme, hideBackground: false, hideBadge: true)
} else if self.hasEmbeddedTitleContent {
presentationTheme = self.presentationData.theme
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: true)
navigationBarTheme = NavigationBarTheme(rootControllerTheme: forcedNavigationBarTheme, hideBackground: false, hideBadge: true, edgeEffectColor: .clear, style: .glass)
} else {
presentationTheme = self.presentationData.theme
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: false, hideBadge: false, edgeEffectColor: .clear, style: .glass)
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), transition: .immediate)
self.chatTitleView?.updateThemeAndStrings(theme: presentationTheme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent)
self.moreBarButton.updateColor(color: presentationTheme.chat.inputPanel.panelControlColor)
}
enum PinnedReferenceMessage {
@@ -6790,9 +6791,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
if !chatNavigationStack.isEmpty {
self.chatDisplayNode.navigationBar?.backButtonNode.isGestureEnabled = true
self.chatDisplayNode.navigationBar?.backButtonNode.activated = { [weak self] gesture, _ in
if !chatNavigationStack.isEmpty, let backButtonNode = self.chatDisplayNode.navigationBar?.backButtonNode as? ContextControllerSourceNode {
backButtonNode.isGestureEnabled = true
backButtonNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let backButtonNode = strongSelf.chatDisplayNode.navigationBar?.backButtonNode, let navigationController = strongSelf.effectiveNavigationController else {
gesture.cancel()
return
@@ -6849,7 +6850,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case .standard(.default) = self.presentationInterfaceState.mode, self.raiseToListen == nil {
self.raiseToListen = RaiseToListenManager(shouldActivate: { [weak self] in
if let strongSelf = self, strongSelf.isNodeLoaded && strongSelf.canReadHistoryValue, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, strongSelf.playlistStateAndType == nil {
if let strongSelf = self, strongSelf.isNodeLoaded && strongSelf.canReadHistoryValue, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, strongSelf.globalControlPanelsContext?.playlistStateAndType == nil {
if !strongSelf.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) {
return false
}
@@ -6890,7 +6891,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.deactivateRaiseGesture()
})
self.raiseToListen?.enabled = self.canReadHistoryValue
self.tempVoicePlaylistEnded = { [weak self] in
self.globalControlPanelsContext?.setTempVoicePlaylistEnded({ [weak self] in
guard let strongSelf = self else {
return
}
@@ -6907,14 +6908,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.returnInputViewFocus = false
strongSelf.chatDisplayNode.ensureInputViewFocused()
}
}
self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in
})
self.globalControlPanelsContext?.setTempVoicePlaylistItemChanged({ [weak self] previousItem, currentItem in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem)
}
})
}
if let arguments = self.presentationArguments as? ChatControllerOverlayPresentationData {
@@ -7189,24 +7190,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controller.navigationPresentation = .modal
controller.setState(.custom(icon: .animation("BroadcastGroup"), title: presentationData.strings.BroadcastGroups_IntroTitle, subtitle: nil, text: presentationData.strings.BroadcastGroups_IntroText, buttonTitle: presentationData.strings.BroadcastGroups_Convert, secondaryButtonTitle: presentationData.strings.BroadcastGroups_Cancel, footerText: nil), animated: false)
controller.proceed = { [weak controller] result in
let attributedTitle = NSAttributedString(string: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
let attributedText = parseMarkdownIntoAttributedString(presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
let alertController = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in
controller?.dismiss()
let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
let _ = (convertGroupToGigagroup(account: context.account, peerId: peerId)
|> deliverOnMainQueue).startStandalone(completed: {
let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: false, action: { _ in return false }), in: .current)
})
})])
let alertController = textAlertController(
context: context,
title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title,
text: presentationData.strings.BroadcastGroups_ConfirmationAlert_Text,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in
controller?.dismiss()
let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
let _ = (convertGroupToGigagroup(account: context.account, peerId: peerId)
|> deliverOnMainQueue).startStandalone(completed: {
let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: false, action: { _ in return false }), in: .current)
})
})
]
)
controller?.present(alertController, in: .window(.root))
}
strongSelf.push(controller)
@@ -7235,7 +7239,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.storedAnimateFromSnapshotState = nil
if let titleViewSnapshotState = snapshotState.titleViewSnapshotState {
self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState)
self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState, direction: .up)
}
if let avatarSnapshotState = snapshotState.avatarSnapshotState {
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState)
@@ -7299,6 +7303,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if #available(iOS 18.0, *) {
} else {
//TODO:release
}
UIView.performWithoutAnimation {
self.view.endEditing(true)
}
@@ -7434,7 +7442,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
super.containerLayoutUpdated(layout, transition: transition)
self.validLayout = layout
self.chatTitleView?.layout = layout
switch self.presentationInterfaceState.mode {
case .standard, .inline:
@@ -8081,64 +8088,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.presentEmojiList(references: [stickerPackReference], previewIconFile: previewIconFile)
}
}
func displayDiceTooltip(dice: TelegramMediaDice) {
guard let _ = dice.value else {
return
}
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
let value: String?
let emoji = dice.emoji.strippedEmoji
switch emoji {
case "🎲":
value = self.presentationData.strings.Conversation_Dice_u1F3B2
case "🎯":
value = self.presentationData.strings.Conversation_Dice_u1F3AF
case "🏀":
value = self.presentationData.strings.Conversation_Dice_u1F3C0
case "":
value = self.presentationData.strings.Conversation_Dice_u26BD
case "🎰":
value = self.presentationData.strings.Conversation_Dice_u1F3B0
case "🎳":
value = self.presentationData.strings.Conversation_Dice_u1F3B3
default:
let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
let key = "Conversation.Dice.u\(emojiHex)"
if let string = self.presentationData.strings.primaryComponent.dict[key] {
value = string
} else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
value = string
} else {
value = nil
}
}
if let value = value {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in
if let self, canSendMessagesToChat(self.presentationInterfaceState), action == .undo {
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
guard let self else {
return
}
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
})
}
return false
}), in: .current)
}
}
func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false) -> [EnqueueMessage] {
var defaultThreadId: Int64?
var defaultReplyMessageSubject: EngineMessageReplySubject?
@@ -8470,9 +8420,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject, let messageLimit = customChatContents.messageLimit {
if let originalHistoryView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, originalHistoryView.entries.count + mappedMessages.count > messageLimit {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_QuickReplyMediaMessageLimitReachedText(Int32(messageLimit)), actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
let alertController = textAlertController(
context: strongSelf.context,
title: nil,
text: strongSelf.presentationData.strings.Chat_QuickReplyMediaMessageLimitReachedText(Int32(messageLimit)),
actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
]
)
strongSelf.present(alertController, in: .window(.root))
return
}
}
@@ -404,8 +404,8 @@ extension ChatControllerImpl {
titleString = self.presentationData.strings.Chat_DeletePaidMessageTon_Title
textString = self.presentationData.strings.Chat_DeletePaidMessageTon_Text
}
self.present(standardTextAlertController(
theme: AlertControllerTheme(presentationData: self.presentationData),
self.present(textAlertController(
context: self.context,
title: titleString,
text: textString,
actions: [
@@ -429,6 +429,7 @@ extension ChatControllerImpl {
return
}
if messageIds.count == 1, let message = messages.values.compactMap({ $0 }).first, let repeatAttribute = message.attributes.first(where: { $0 is ScheduledRepeatAttribute }) as? ScheduledRepeatAttribute {
let commit = { [weak self] in
guard let self else {
@@ -449,8 +450,8 @@ extension ChatControllerImpl {
deleteOneAction = self.presentationData.strings.ScheduledMessages_DeleteRepeatingActionSingle
deleteAllAction = self.presentationData.strings.ScheduledMessages_DeleteRepeatingActionMultiple
}
self.present(standardTextAlertController(
theme: AlertControllerTheme(presentationData: self.presentationData),
self.present(textAlertController(
context: self.context,
title: title,
text: text,
actions: [
@@ -519,32 +519,44 @@ extension ChatControllerImpl {
if case .reply = info {
let titleContent: ChatTitleContent
if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote {
titleContent = .custom(strings.Chat_TitleQuoteSelection, subtitleText, false)
titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleQuoteSelection))], subtitle: subtitleText, isEnabled: false)
} else {
titleContent = .custom(strings.Chat_TitleReply, subtitleText, false)
titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleReply))], subtitle: subtitleText, isEnabled: false)
}
strongSelf.state.chatTitleContent = titleContent
} else if case .link = info {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitleLinkOptions, subtitleText, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleLinkOptions))], subtitle: subtitleText, isEnabled: false)
} else if displayedCount == 1 {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Conversation_ForwardOptions_ForwardTitleSingle))], subtitle: subtitleText, isEnabled: false)
} else {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1))))], subtitle: subtitleText, isEnabled: false)
}
} else if let selectionState = configuration.selectionState {
if selectionState.selectedIds.count > 0 {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false)
let rawText = strings.Conversation_SelectedMessagesFormat(Int32(selectionState.selectedIds.count))
var items: [ChatTitleContent.TitleTextItem] = []
if let range = rawText.range(of: "{}") {
if range.lowerBound != rawText.startIndex {
items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_0"), content: .text(String(rawText[rawText.startIndex ..< range.lowerBound]))))
}
items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_1"), content: .number(selectionState.selectedIds.count, minDigits: 1)))
if range.upperBound != rawText.endIndex {
items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_2"), content: .text(String(rawText[range.upperBound ..< rawText.endIndex]))))
}
}
strongSelf.state.chatTitleContent = .custom(title: items, subtitle: nil, isEnabled: false)
} else {
if let reportReason = configuration.reportReason {
strongSelf.state.chatTitleContent = .custom(reportReason.title, strings.Conversation_SelectMessages, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(1), content: .text(reportReason.title))], subtitle: strings.Conversation_SelectMessages, isEnabled: false)
} else {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectMessages, nil, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(2), content: .text(strings.Conversation_SelectMessages))], subtitle: nil, isEnabled: false)
}
}
} else if let peer = peerViewMainPeer(peerView) {
if case .pinnedMessages = configuration.subject {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1))))], subtitle: nil, isEnabled: false)
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(
@@ -557,7 +569,7 @@ extension ChatControllerImpl {
cachedData: nil
), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true)
} else {
strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(channel.debugDisplayTitle))], subtitle: nil, isEnabled: true)
}
} else {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
@@ -1549,7 +1561,7 @@ extension ChatControllerImpl {
}
strongSelf.state.infoAvatar = .emojiStatus(content: avatarContent, contextActionIsEnabled: infoContextActionIsEnabled)
} else if chatLocation.threadId == EngineMessage.newTopicThreadId {
strongSelf.state.chatTitleContent = .custom(strongSelf.presentationData.strings.Chat_MessageHeaderBotNewThread, nil, false)
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strongSelf.presentationData.strings.Chat_MessageHeaderBotNewThread))], subtitle: nil, isEnabled: false)
strongSelf.state.infoAvatar = nil
} else {
strongSelf.state.chatTitleContent = .replyThread(type: replyThreadType, count: count)
@@ -1742,11 +1754,11 @@ extension ChatControllerImpl {
case let .quickReplyMessageInput(shortcut, shortcutType):
switch shortcutType {
case .generic:
self.state.chatTitleContent = .custom("\(shortcut)", nil, false)
self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(shortcut)"))], subtitle: nil, isEnabled: false)
case .greeting:
self.state.chatTitleContent = .custom(strings.QuickReply_TitleGreetingMessage, nil, false)
self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.QuickReply_TitleGreetingMessage))], subtitle: nil, isEnabled: false)
case .away:
self.state.chatTitleContent = .custom(strings.QuickReply_TitleAwayMessage, nil, false)
self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.QuickReply_TitleAwayMessage))], subtitle: nil, isEnabled: false)
}
case let .businessLinkSetup(link):
let linkUrl: String
@@ -1756,10 +1768,10 @@ extension ChatControllerImpl {
linkUrl = link.url
}
self.state.chatTitleContent = .custom(link.title ?? strings.Business_Links_EditLinkTitle, linkUrl, false)
self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(link.title ?? strings.Business_Links_EditLinkTitle))], subtitle: linkUrl, isEnabled: false)
}
} else {
self.state.chatTitleContent = .custom(" ", nil, false)
self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(" "))], subtitle: nil, isEnabled: false)
}
self.peerDisposable = (peerView
@@ -0,0 +1,124 @@
import Foundation
import AccountContext
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
import TelegramPresentationData
import PresentationDataUtils
import UndoUI
import EmojiGameStakeScreen
import ChatPresentationInterfaceState
import TelegramStringFormatting
extension ChatControllerImpl {
func presentEmojiGameStake() {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame())
|> deliverOnMainQueue).start(next: { [weak self] gameInfo in
guard let self, case let .available(info) = gameInfo else {
return
}
let controller = EmojiGameStakeScreen(
context: self.context,
gameInfo: info,
completion: { [weak self] stake in
guard let self else {
return
}
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
guard let self else {
return
}
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: "🎲", tonAmount: stake.value > 0 ? stake.value : nil)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
})
}
)
self.push(controller)
})
}
func displayDiceTooltip(dice: TelegramMediaDice) {
guard let _ = dice.value else {
return
}
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
let emoji = dice.emoji.strippedEmoji
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame())
|> deliverOnMainQueue).start(next: { [weak self] gameInfo in
guard let self else {
return
}
let canSendMessages = canSendMessagesToChat(self.presentationInterfaceState)
let value: String?
var changeAction: String?
var tonAmount: Int64?
if canSendMessages, emoji == "🎲", case let .available(info) = gameInfo {
let currentStake = info.previousStake
value = "\(self.presentationData.strings.Conversation_Dice_Stake) $ \(formatTonAmountText(currentStake, dateTimeFormat: self.presentationData.dateTimeFormat))"
changeAction = self.presentationData.strings.Conversation_Dice_Change
tonAmount = info.previousStake
} else {
switch emoji {
case "🎲":
value = self.presentationData.strings.Conversation_Dice_u1F3B2
case "🎯":
value = self.presentationData.strings.Conversation_Dice_u1F3AF
case "🏀":
value = self.presentationData.strings.Conversation_Dice_u1F3C0
case "":
value = self.presentationData.strings.Conversation_Dice_u26BD
case "🎰":
value = self.presentationData.strings.Conversation_Dice_u1F3B0
case "🎳":
value = self.presentationData.strings.Conversation_Dice_u1F3B3
default:
let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
let key = "Conversation.Dice.u\(emojiHex)"
if let string = self.presentationData.strings.primaryComponent.dict[key] {
value = string
} else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
value = string
} else {
value = nil
}
}
}
if let value = value {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessages ? self.presentationData.strings.Conversation_SendDice : nil, changeAction: changeAction), elevatedLayout: false, action: { [weak self] action in
if let self, canSendMessagesToChat(self.presentationInterfaceState) {
switch action {
case .undo:
self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
guard let self else {
return
}
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji, tonAmount: tonAmount)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
})
case .info:
if let _ = changeAction {
self.presentEmojiGameStake()
}
default:
break
}
}
return false
}), in: .current)
}
})
}
}
@@ -9,6 +9,7 @@ import TelegramPresentationData
import PresentationDataUtils
import QuickReplyNameAlertController
import BusinessLinkNameAlertController
import ChatTitleView
extension ChatControllerImpl {
func editChat() {
@@ -48,7 +49,16 @@ extension ChatControllerImpl {
contentNode.setErrorText(errorText: self.presentationData.strings.QuickReply_ShortcutExistsInlineError)
}
} else {
self.chatTitleView?.titleContent = .custom("\(value)", nil, false)
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(value)"))], subtitle: nil, isEnabled: false),
transition: .immediate
)
alertController?.view.endEditing(true)
alertController?.dismissAnimated()
@@ -66,22 +76,19 @@ extension ChatControllerImpl {
var completion: ((String?) -> Void)?
let alertController = businessLinkNameAlertController(
context: self.context,
text: self.presentationData.strings.Business_Links_LinkNameTitle,
subtext: self.presentationData.strings.Business_Links_LinkNameText,
value: currentValue,
characterLimit: 32,
apply: { value in
completion?(value)
}
)
completion = { [weak self, weak alertController] value in
guard let self else {
alertController?.dismissAnimated()
alertController?.dismiss(completion: nil)
return
}
if let value {
if value == currentValue {
alertController?.dismissAnimated()
alertController?.dismiss(completion: nil)
return
}
@@ -93,13 +100,21 @@ extension ChatControllerImpl {
} else {
linkUrl = link.url
}
self.chatTitleView?.titleContent = .custom(value.isEmpty ? self.presentationData.strings.Business_Links_EditLinkTitle : value, linkUrl, false)
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(value.isEmpty ? self.presentationData.strings.Business_Links_EditLinkTitle : value))], subtitle: linkUrl, isEnabled: false),
transition: .immediate
)
if case let .customChatContents(customChatContents) = self.subject {
customChatContents.businessLinkUpdate(message: link.message, entities: link.entities, title: value.isEmpty ? nil : value)
}
alertController?.view.endEditing(true)
alertController?.dismissAnimated()
alertController?.dismiss(completion: nil)
}
}
self.present(alertController, in: .window(.root))
@@ -51,6 +51,16 @@ import ChatThemeScreen
import ChatTextInputPanelNode
import ChatInputAccessoryPanel
import ChatMessageTextBubbleContentNode
import HeaderPanelContainerComponent
import MediaPlaybackHeaderPanelComponent
import LiveLocationHeaderPanelComponent
import TranslateHeaderPanelComponent
import AdPanelHeaderPanelComponent
import MessageFeeHeaderPanelComponent
import LegacyChatHeaderPanelComponent
import ChatSearchNavigationContentNode
import GroupCallHeaderPanelComponent
import PresentationDataUtils
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode
@@ -119,7 +129,9 @@ class HistoryNodeContainer: ASDisplayNode {
var isSecret: Bool {
didSet {
if self.isSecret != oldValue {
setLayerDisableScreenshots(self.layer, self.isSecret)
// MISC: Bypass screenshot protection if enabled
let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection
setLayerDisableScreenshots(self.layer, shouldDisable)
}
}
}
@@ -134,7 +146,9 @@ class HistoryNodeContainer: ASDisplayNode {
super.init()
if self.isSecret {
setLayerDisableScreenshots(self.layer, self.isSecret)
// MISC: Bypass screenshot protection if enabled
let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection
setLayerDisableScreenshots(self.layer, shouldDisable)
}
}
}
@@ -218,26 +232,29 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
private let inputPanelClippingNode: SparseNode
let inputPanelBackgroundNode: NavigationBackgroundNode
private var navigationBarBackgroundContent: WallpaperBubbleBackgroundNode?
private var intrinsicInputPanelBackgroundNodeSize: CGSize?
private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0
private var plainInputSeparatorAlpha: CGFloat?
private var usePlainInputSeparator: Bool
private var chatImportStatusPanel: ChatImportStatusPanel?
private(set) var adPanelNode: ChatAdPanelNode?
private(set) var feePanelNode: ChatFeePanelNode?
var adPanelMessage: Message? {
guard let headerPanelsComponentView = self.headerPanelsView?.view as? HeaderPanelContainerComponent.View else {
return nil
}
guard let adPanelView = headerPanelsComponentView.panel(forKey: AnyHashable("ad")) as? AdPanelHeaderPanelComponent.View else {
return nil
}
return adPanelView.message?._asMessage()
}
private let titleAccessoryPanelContainer: ChatControllerTitlePanelNodeContainer
private var titleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
private var chatTranslationPanel: ChatTranslationPanelNode?
private var currentTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
private var floatingTopicsPanelContainer: ChatControllerTitlePanelNodeContainer
private var floatingTopicsPanel: (view: ComponentView<ChatSidePanelEnvironment>, component: ChatFloatingTopicsPanel)?
private var headerPanelsView: ComponentView<Empty>?
private var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
private var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
private(set) var inputPanelNode: ChatInputPanelNode?
@@ -1091,7 +1108,18 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if self.inputPanelContainerNode.expansionFraction > 0.3 {
statusBar.updateStatusBarStyle(.White, animated: true)
} else {
statusBar.updateStatusBarStyle(self.chatPresentationInterfaceState.theme.rootController.statusBarStyle.style, animated: true)
let statusBarStyle: StatusBarStyle
if let isDark = self.backgroundNode.isDark {
if isDark {
statusBarStyle = .White
} else {
statusBarStyle = .Black
}
} else {
statusBarStyle = self.chatPresentationInterfaceState.theme.rootController.statusBarStyle.style
}
statusBar.updateStatusBarStyle(statusBarStyle, animated: true)
}
self.controller?.deferScreenEdgeGestures = []
case .overlay:
@@ -1318,7 +1346,275 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight)
var headerPanels: [HeaderPanelContainerComponent.Panel] = []
if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 0,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
data: mediaPlayback,
controller: { [weak self] in
return self?.controller
}
)))
)
}
if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "liveLocation",
orderIndex: 1,
component: AnyComponent(LiveLocationHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
data: liveLocation,
controller: { [weak self] in
return self?.controller
}
)))
)
}
if let groupCall = self.controller?.globalControlPanelsContextState?.groupCall {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "groupCall",
orderIndex: 2,
component: AnyComponent(GroupCallHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
data: groupCall,
onTapAction: { [weak self] in
guard let self, let groupCall = self.controller?.globalControlPanelsContextState?.groupCall else {
return
}
self.controller?.joinGroupCall(
peerId: groupCall.peerId,
invite: nil,
activeCall: EngineGroupCallDescription(
id: groupCall.info.id,
accessHash: groupCall.info.accessHash,
title: groupCall.info.title,
scheduleTimestamp: groupCall.info.scheduleTimestamp,
subscribedToScheduled: groupCall.info.subscribedToScheduled,
isStream: groupCall.info.isStream
)
)
},
onNotifyScheduledTapAction: { [weak self] in
guard let self, let controller = self.controller, let groupCall = self.controller?.globalControlPanelsContextState?.groupCall else {
return
}
if groupCall.info.scheduleTimestamp != nil && !groupCall.info.subscribedToScheduled {
let _ = self.context.engine.calls.toggleScheduledGroupCallSubscription(peerId: groupCall.peerId, reference: .id(id: groupCall.info.id, accessHash: groupCall.info.accessHash), subscribe: true).startStandalone()
controller.controllerInteraction?.displayUndo(
.universal(
animation: "anim_set_notification",
scale: 0.06,
colors: [
"Middle.Group 1.Fill 1": UIColor.white,
"Top.Group 1.Fill 1": UIColor.white,
"Bottom.Group 1.Fill 1": UIColor.white,
"EXAMPLE.Group 1.Fill 1": UIColor.white,
"Line.Group 1.Stroke 1": UIColor.white
],
title: nil,
text: controller.presentationData.strings.Chat_ToastSubscribedToScheduledLiveStream_Text,
customUndoText: nil,
timeout: nil
)
)
}
}
)))
)
}
var hasTranslationPanel = false
if let _ = self.chatPresentationInterfaceState.translationState, self.emptyType == nil {
if case .overlay = self.chatPresentationInterfaceState.mode {
} else if self.chatPresentationInterfaceState.renderedPeer?.peer?.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) != nil {
} else if self.chatPresentationInterfaceState.search != nil {
} else {
hasTranslationPanel = true
}
}
var displayAdPanel = false
if let _ = self.chatPresentationInterfaceState.adMessage {
if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState {
if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil && !self.chatPresentationInterfaceState.peerIsBlocked && self.chatPresentationInterfaceState.hasAtLeast3Messages {
displayAdPanel = true
}
}
}
if displayAdPanel, let adMessage = self.chatPresentationInterfaceState.adMessage {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "ad",
orderIndex: 2,
component: AnyComponent(AdPanelHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
info: AdPanelHeaderPanelComponent.Info(
message: EngineMessage(adMessage)
),
action: { [weak self] message in
guard let self else {
return
}
self.controllerInteraction.activateAdAction(message.id, nil, false, false)
},
contextAction: { [weak self] message, sourceNode, gesture in
guard let self else {
return
}
self.controllerInteraction.adContextAction(message._asMessage(), sourceNode, gesture)
},
close: { [weak self] in
guard let self, let adMessage = self.chatPresentationInterfaceState.adMessage else {
return
}
if self.context.isPremium, let adAttribute = adMessage.adAttribute {
self.controllerInteraction.removeAd(adAttribute.opaqueId)
} else {
self.controllerInteraction.openNoAdsDemo()
}
}
)))
)
}
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.currentTitleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
self.currentTitleAccessoryPanelNode = titleAccessoryPanelNode
let panelKey = "\(type(of: titleAccessoryPanelNode))"
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: panelKey,
orderIndex: 3,
component: AnyComponent(LegacyChatHeaderPanelComponent(
panelNode: titleAccessoryPanelNode,
interfaceState: self.chatPresentationInterfaceState
)))
)
} else {
self.currentTitleAccessoryPanelNode = nil
}
var displayFeePanel: (value: Int64, peer: EnginePeer)?
if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState {
if let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo == nil {
if !self.chatPresentationInterfaceState.peerIsBlocked, let paidMessageStars = self.chatPresentationInterfaceState.contactStatus?.peerStatusSettings?.paidMessageStars, paidMessageStars.value > 0 {
displayFeePanel = (paidMessageStars.value, .user(user))
}
} else if let removePaidMessageFeeData = self.chatPresentationInterfaceState.removePaidMessageFeeData {
displayFeePanel = (removePaidMessageFeeData.amount.value, removePaidMessageFeeData.peer)
}
}
if let displayFeePanel {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "fee",
orderIndex: 4,
component: AnyComponent(MessageFeeHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
info: MessageFeeHeaderPanelComponent.Info(
value: displayFeePanel.value,
peer: displayFeePanel.peer
),
removeFee: { [weak self] in
guard let self else {
return
}
self.controllerInteraction.openMessageFeeException()
},
)))
)
}
if hasTranslationPanel, let translationState = self.chatPresentationInterfaceState.translationState {
headerPanels.append(HeaderPanelContainerComponent.Panel(
key: "translate",
orderIndex: 5,
component: AnyComponent(TranslateHeaderPanelComponent(
context: self.context,
theme: self.chatPresentationInterfaceState.theme,
strings: self.chatPresentationInterfaceState.strings,
info: TranslateHeaderPanelComponent.Info(
isPremium: self.chatPresentationInterfaceState.isPremium,
isActive: translationState.isEnabled,
fromLang: translationState.fromLang,
toLang: translationState.toLang,
peer: (self.chatPresentationInterfaceState.renderedPeer?.chatMainPeer).flatMap(EnginePeer.init)
),
close: { [weak self] in
guard let self else {
return
}
self.interfaceInteraction?.hideTranslationPanel()
},
toggle: { [weak self] in
guard let self, let translationState = self.chatPresentationInterfaceState.translationState else {
return
}
self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated)
},
changeLanguage: { [weak self] code in
guard let self else {
return
}
self.interfaceInteraction?.changeTranslationLanguage(code)
},
addDoNotTranslateLanguage: { [weak self] code in
guard let self else {
return
}
self.interfaceInteraction?.addDoNotTranslateLanguage(code)
},
controller: { [weak self] in
return self?.controller
}
)))
)
}
var floatingTopicsPanelInsets = UIEdgeInsets()
var headerPanelsSize: CGSize?
if !headerPanels.isEmpty {
let headerPanelsView: ComponentView<Empty>
var headerPanelsTransition = ComponentTransition(transition)
if let current = self.headerPanelsView {
headerPanelsView = current
} else {
headerPanelsTransition = headerPanelsTransition.withAnimation(.none)
headerPanelsView = ComponentView()
self.headerPanelsView = headerPanelsView
}
let headerPanelsSizeValue = headerPanelsView.update(
transition: headerPanelsTransition,
component: AnyComponent(HeaderPanelContainerComponent(
theme: self.chatPresentationInterfaceState.theme,
tabs: nil,
panels: headerPanels
)),
environment: {},
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
)
headerPanelsSize = headerPanelsSizeValue
floatingTopicsPanelInsets.top += headerPanelsSizeValue.height
} else if let headerPanelsView = self.headerPanelsView {
self.headerPanelsView = nil
if let headerPanelsComponentView = headerPanelsView.view {
transition.updateAlpha(layer: headerPanelsComponentView.layer, alpha: 0.0, completion: { [weak headerPanelsComponentView] _ in
headerPanelsComponentView?.removeFromSuperview()
})
}
}
var dismissedFloatingTopicsPanel: (view: ComponentView<ChatSidePanelEnvironment>, component: ChatFloatingTopicsPanel)?
var immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance = false
var didChangeFloatingTopicsPanel = false
@@ -1337,215 +1633,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
switch floatingTopicsPanelComponent.location {
case .side:
floatingTopicsPanelInsets.left = 72.0 + 8.0 + 8.0
floatingTopicsPanelInsets.left += 72.0 + 10.0 + 10.0
case .top:
floatingTopicsPanelInsets.top = 40.0 + 8.0
floatingTopicsPanelInsets.top += 40.0 + 10.0
}
} else if let floatingTopicsPanel = self.floatingTopicsPanel {
self.floatingTopicsPanel = nil
dismissedFloatingTopicsPanel = floatingTopicsPanel
}
var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false
var titleAccessoryPanelHeight: CGFloat?
var titleAccessoryPanelBackgroundHeight: CGFloat?
var titleAccessoryPanelHitTestSlop: CGFloat?
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
if self.titleAccessoryPanelNode != titleAccessoryPanelNode {
dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode
self.titleAccessoryPanelNode = titleAccessoryPanelNode
immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = true
self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode)
titleAccessoryPanelNode.clipsToBounds = true
}
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
titleAccessoryPanelHeight = layoutResult.insetHeight
titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight
titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop
if immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance {
if transition.isAnimated {
titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
titleAccessoryPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -layoutResult.backgroundHeight, 0.0)
transition.updateSublayerTransformOffset(layer: titleAccessoryPanelNode.layer, offset: CGPoint())
}
} else if let titleAccessoryPanelNode = self.titleAccessoryPanelNode {
dismissedTitleAccessoryPanelNode = titleAccessoryPanelNode
self.titleAccessoryPanelNode = nil
}
var dismissedTranslationPanelNode: ChatTranslationPanelNode?
var immediatelyLayoutTranslationPanelNodeAndAnimateAppearance = false
var translationPanelHeight: CGFloat?
var hasTranslationPanel = false
if let _ = self.chatPresentationInterfaceState.translationState, self.emptyType == nil {
if case .overlay = self.chatPresentationInterfaceState.mode {
} else if self.chatPresentationInterfaceState.renderedPeer?.peer?.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) != nil {
} else if self.chatPresentationInterfaceState.search != nil {
} else {
hasTranslationPanel = true
}
}
/*#if DEBUG
if "".isEmpty {
hasTranslationPanel = true
}
#endif*/
if hasTranslationPanel {
let translationPanelNode: ChatTranslationPanelNode
if let current = self.chatTranslationPanel {
translationPanelNode = current
} else {
translationPanelNode = ChatTranslationPanelNode(context: self.context)
}
translationPanelNode.interfaceInteraction = self.interfaceInteraction
if self.chatTranslationPanel != translationPanelNode {
dismissedTranslationPanelNode = self.chatTranslationPanel
self.chatTranslationPanel = translationPanelNode
immediatelyLayoutTranslationPanelNodeAndAnimateAppearance = true
self.titleAccessoryPanelContainer.addSubnode(translationPanelNode)
translationPanelNode.clipsToBounds = true
}
let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, leftDisplayInset: 0.0, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
translationPanelHeight = height
if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
translationPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0)
transition.updateSublayerTransformOffset(layer: translationPanelNode.layer, offset: CGPoint())
}
} else if let chatTranslationPanel = self.chatTranslationPanel {
dismissedTranslationPanelNode = chatTranslationPanel
self.chatTranslationPanel = nil
}
var dismissedImportStatusPanelNode: ChatImportStatusPanel?
var importStatusPanelHeight: CGFloat?
if let importState = self.chatPresentationInterfaceState.importState {
let importStatusPanelNode: ChatImportStatusPanel
if let current = self.chatImportStatusPanel {
importStatusPanelNode = current
} else {
importStatusPanelNode = ChatImportStatusPanel()
}
if self.chatImportStatusPanel != importStatusPanelNode {
dismissedImportStatusPanelNode = self.chatImportStatusPanel
self.chatImportStatusPanel = importStatusPanelNode
self.contentContainerNode.contentNode.addSubnode(importStatusPanelNode)
}
importStatusPanelHeight = importStatusPanelNode.update(context: self.context, progress: CGFloat(importState.progress), presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: self.chatPresentationInterfaceState.theme, wallpaper: self.chatPresentationInterfaceState.chatWallpaper), fontSize: self.chatPresentationInterfaceState.fontSize, strings: self.chatPresentationInterfaceState.strings, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), width: layout.size.width)
} else if let importStatusPanelNode = self.chatImportStatusPanel {
dismissedImportStatusPanelNode = importStatusPanelNode
self.chatImportStatusPanel = nil
}
var dismissedAdPanelNode: ChatAdPanelNode?
var adPanelHeight: CGFloat?
var displayAdPanel = false
if let _ = self.chatPresentationInterfaceState.adMessage {
if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState {
if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil && !self.chatPresentationInterfaceState.peerIsBlocked && self.chatPresentationInterfaceState.hasAtLeast3Messages {
displayAdPanel = true
}
}
}
if displayAdPanel {
var animateAppearance = false
let adPanelNode: ChatAdPanelNode
if let current = self.adPanelNode {
adPanelNode = current
} else {
adPanelNode = ChatAdPanelNode(context: self.context, animationCache: self.controllerInteraction.presentationContext.animationCache, animationRenderer: self.controllerInteraction.presentationContext.animationRenderer)
adPanelNode.controllerInteraction = self.controllerInteraction
adPanelNode.clipsToBounds = true
animateAppearance = true
}
if self.adPanelNode != adPanelNode {
dismissedAdPanelNode = self.adPanelNode
self.adPanelNode = adPanelNode
self.titleAccessoryPanelContainer.addSubnode(adPanelNode)
}
let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState)
if let adMessage = self.chatPresentationInterfaceState.adMessage, let opaqueId = adMessage.adAttribute?.opaqueId {
self.historyNode.markAdAsSeen(opaqueId: opaqueId)
}
adPanelHeight = height
if transition.isAnimated && animateAppearance {
adPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
adPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0)
transition.updateSublayerTransformOffset(layer: adPanelNode.layer, offset: CGPoint())
}
} else if let adPanelNode = self.adPanelNode {
dismissedAdPanelNode = adPanelNode
self.adPanelNode = nil
}
var dismissedFeePanelNode: ChatFeePanelNode?
var feePanelHeight: CGFloat?
var displayFeePanel = false
if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState {
if let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo == nil {
if !self.chatPresentationInterfaceState.peerIsBlocked, let paidMessageStars = self.chatPresentationInterfaceState.contactStatus?.peerStatusSettings?.paidMessageStars, paidMessageStars.value > 0 {
displayFeePanel = true
}
} else if self.chatPresentationInterfaceState.removePaidMessageFeeData != nil {
displayFeePanel = true
}
}
var immediatelyLayoutFeePanelNodeAndAnimateAppearance = false
if displayFeePanel {
var animateAppearance = false
let feePanelNode: ChatFeePanelNode
if let current = self.feePanelNode {
feePanelNode = current
} else {
feePanelNode = ChatFeePanelNode(context: self.context)
feePanelNode.controllerInteraction = self.controllerInteraction
feePanelNode.clipsToBounds = true
animateAppearance = true
}
if self.feePanelNode != feePanelNode {
dismissedFeePanelNode = self.feePanelNode
self.feePanelNode = feePanelNode
self.titleAccessoryPanelContainer.addSubnode(feePanelNode)
}
let height = feePanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, leftDisplayInset: 0.0, transition: animateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
feePanelHeight = height
if transition.isAnimated && animateAppearance {
immediatelyLayoutFeePanelNodeAndAnimateAppearance = true
}
if immediatelyLayoutFeePanelNodeAndAnimateAppearance {
feePanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
feePanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0)
transition.updateSublayerTransformOffset(layer: feePanelNode.layer, offset: CGPoint())
}
} else if let feePanelNode = self.feePanelNode {
dismissedFeePanelNode = feePanelNode
self.feePanelNode = nil
}
var isSidebarOpen = false
if let floatingTopicsPanel = self.floatingTopicsPanel {
isSidebarOpen = floatingTopicsPanel.component.location == .side
@@ -1830,54 +1926,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
var extraNavigationBarHeight: CGFloat = 0.0
var extraNavigationBarHitTestSlop: CGFloat = 0.0
var titlePanelsContentOffset: CGFloat = 0.0
var titleAccessoryPanelFrame: CGRect?
let titleAccessoryPanelBaseY = titlePanelsContentOffset
if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight {
titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += titleAccessoryPanelBackgroundHeight ?? 0.0
extraNavigationBarHitTestSlop = titleAccessoryPanelHitTestSlop ?? 0.0
titlePanelsContentOffset += panelHeight
}
updateExtraNavigationBarBackgroundHeight(0.0, 0.0, nil, transition)
let feePanelBaseY = titlePanelsContentOffset
var translationPanelFrame: CGRect?
if let _ = self.chatTranslationPanel, let panelHeight = translationPanelHeight {
translationPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += panelHeight
}
var importStatusPanelFrame: CGRect?
if let _ = self.chatImportStatusPanel, let panelHeight = importStatusPanelHeight {
importStatusPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
}
var adPanelFrame: CGRect?
if let _ = self.adPanelNode, let panelHeight = adPanelHeight {
adPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += panelHeight
}
var feePanelFrame: CGRect?
if let _ = self.feePanelNode, let panelHeight = feePanelHeight {
feePanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += panelHeight
}
updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, nil, transition)
let sidePanelTopInset: CGFloat = insets.top
var sidePanelTopInset: CGFloat = insets.top + 4.0
let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom)
@@ -2129,7 +2181,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
additionalOffset = 80.0
}
if let _ = inputPanelSize {
inputPanelHideOffset += -40.0 - additionalOffset
inputPanelHideOffset += -48.0 - additionalOffset
}
if let accessoryPanelSize = accessoryPanelSize {
inputPanelHideOffset += -accessoryPanelSize.height - additionalOffset
@@ -2146,7 +2198,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if self.secondaryInputPanelNode != nil {
secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - secondaryInputPanelSize!.height - 8.0), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height))
secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - 8.0 - secondaryInputPanelSize!.height - 8.0), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height))
if self.dismissedAsOverlay {
secondaryInputPanelFrame!.origin.y = layout.size.height
}
@@ -2199,12 +2251,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let bottomBackgroundEdgeEffectNode {
var blurFrame = inputBackgroundFrame
blurFrame.origin.y -= 26.0
blurFrame.origin.y -= 18.0
blurFrame.size.height = max(100.0, layout.size.height - blurFrame.origin.y)
transition.updateFrame(node: bottomBackgroundEdgeEffectNode, frame: blurFrame)
bottomBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 80.0),
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 100.0),
blur: false,
containerSize: wallpaperBounds.size,
transition: transition
)
@@ -2328,15 +2381,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if displayTopDimNode {
var topInset = listInsets.bottom + UIScreenPixel
if let titleAccessoryPanelHeight = titleAccessoryPanelHeight {
if expandTopDimNode {
topInset -= titleAccessoryPanelHeight
} else {
topInset -= UIScreenPixel
}
}
let inputPanelOrigin = layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight
if expandTopDimNode {
@@ -2399,6 +2443,29 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
})
var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if let current = self.topBackgroundEdgeEffectNode {
topBackgroundEdgeEffectNode = current
} else {
if let value = self.backgroundNode.makeEdgeEffectNode() {
topBackgroundEdgeEffectNode = value
self.topBackgroundEdgeEffectNode = value
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
}
}
if let topBackgroundEdgeEffectNode {
var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + 10.0)))
blurFrame.origin.y = listInsets.bottom + 10.0 - blurFrame.height
transition.updateFrame(node: topBackgroundEdgeEffectNode, frame: blurFrame)
topBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 100.0),
blur: false,
containerSize: wallpaperBounds.size,
transition: transition
)
}
if self.isScrollingLockedAtTop {
switch self.historyNode.visibleContentOffset() {
case let .known(value) where value <= CGFloat.ulpOfOne:
@@ -2454,6 +2521,18 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateBounds(node: self.inputPanelOverlayNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: layout.size), beginWithCurrentState: true)
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
if let headerPanelsComponentView = self.headerPanelsView?.view, let headerPanelsSize {
let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: headerPanelsSize)
var headerPanelsTransition = ComponentTransition(transition)
if headerPanelsComponentView.superview == nil {
headerPanelsTransition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0)
headerPanelsTransition = headerPanelsTransition.withAnimation(.none)
self.floatingTopicsPanelContainer.view.addSubview(headerPanelsComponentView)
}
headerPanelsTransition.setFrame(view: headerPanelsComponentView, frame: headerPanelsFrame)
sidePanelTopInset += headerPanelsSize.height + 2.0
}
let floatingTopicsPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height))
transition.updateFrame(node: self.floatingTopicsPanelContainer, frame: floatingTopicsPanelContainerFrame)
if let floatingTopicsPanel = self.floatingTopicsPanel {
@@ -2502,11 +2581,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
})*/
dismissedFloatingTopicsPanelView.removeFromSuperview()
}
if let navigationBarBackgroundContent = self.navigationBarBackgroundContent {
transition.updateFrame(node: navigationBarBackgroundContent, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), beginWithCurrentState: true)
navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), within: layout.size, transition: transition)
}
transition.updateFrame(node: self.contentDimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: apparentInputBackgroundFrame.origin.y)))
@@ -2517,47 +2591,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition)
if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let titleAccessoryPanelFrame, !titleAccessoryPanelNode.frame.equalTo(titleAccessoryPanelFrame) {
let previousFrame = titleAccessoryPanelNode.frame
titleAccessoryPanelNode.frame = titleAccessoryPanelFrame
if transition.isAnimated && previousFrame.width != titleAccessoryPanelFrame.width {
} else if immediatelyLayoutAccessoryPanelAndAnimateAppearance {
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height))
} else if previousFrame.minY != titleAccessoryPanelFrame.minY {
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - titleAccessoryPanelFrame.minY))
}
}
if let chatTranslationPanel = self.chatTranslationPanel, let translationPanelFrame, !chatTranslationPanel.frame.equalTo(translationPanelFrame) {
let previousFrame = chatTranslationPanel.frame
chatTranslationPanel.frame = translationPanelFrame
if transition.isAnimated && previousFrame.width != translationPanelFrame.width {
} else if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance {
transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: -translationPanelFrame.height))
} else if previousFrame.minY != translationPanelFrame.minY {
transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: previousFrame.minY - translationPanelFrame.minY))
}
}
if let chatImportStatusPanel = self.chatImportStatusPanel, let importStatusPanelFrame, !chatImportStatusPanel.frame.equalTo(importStatusPanelFrame) {
chatImportStatusPanel.frame = importStatusPanelFrame
}
if let adPanelNode = self.adPanelNode, let adPanelFrame, !adPanelNode.frame.equalTo(adPanelFrame) {
adPanelNode.frame = adPanelFrame
}
if let feePanelNode = self.feePanelNode, let feePanelFrame, !feePanelNode.frame.equalTo(feePanelFrame) {
let previousFrame = feePanelNode.frame
feePanelNode.frame = feePanelFrame
if transition.isAnimated && previousFrame.width != feePanelFrame.width {
} else if immediatelyLayoutFeePanelNodeAndAnimateAppearance {
transition.animatePositionAdditive(node: feePanelNode, offset: CGPoint(x: 0.0, y: -feePanelFrame.height))
} else if previousFrame.minY != feePanelFrame.minY {
transition.animatePositionAdditive(node: feePanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - feePanelFrame.minY))
}
}
if let secondaryInputPanelNode = self.secondaryInputPanelNode, let apparentSecondaryInputPanelFrame = apparentSecondaryInputPanelFrame, !secondaryInputPanelNode.frame.equalTo(apparentSecondaryInputPanelFrame) {
if immediatelyLayoutSecondaryInputPanelAndAnimateAppearance {
@@ -2599,10 +2632,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
}
if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme {
transition.updateFrame(node: inputContextPanelNode, frame: panelFrame)
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: 0.0, transition: transition, interfaceState: self.chatPresentationInterfaceState)
}
transition.updateFrame(node: inputContextPanelNode, frame: panelFrame)
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: transition, interfaceState: self.chatPresentationInterfaceState)
}
if let overlayContextPanelNode = self.overlayContextPanelNode {
@@ -2640,60 +2671,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
if let dismissedTitleAccessoryPanelNode {
var dismissedPanelFrame = dismissedTitleAccessoryPanelNode.frame
transition.updateSublayerTransformOffset(layer: dismissedTitleAccessoryPanelNode.layer, offset: CGPoint(x: 0.0, y: -dismissedPanelFrame.height))
dismissedPanelFrame.origin.y = titleAccessoryPanelBaseY
dismissedTitleAccessoryPanelNode.clipsToBounds = true
dismissedPanelFrame.size.height = 0.0
if transition.isAnimated {
dismissedTitleAccessoryPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
transition.updateFrame(node: dismissedTitleAccessoryPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedTitleAccessoryPanelNode] _ in
dismissedTitleAccessoryPanelNode?.removeFromSupernode()
})
}
if let dismissedTranslationPanelNode {
var dismissedPanelFrame = dismissedTranslationPanelNode.frame
dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height
transition.updateAlpha(node: dismissedTranslationPanelNode, alpha: 0.0, completion: { [weak dismissedTranslationPanelNode] _ in
dismissedTranslationPanelNode?.removeFromSupernode()
})
dismissedTranslationPanelNode.animateOut()
}
if let dismissedImportStatusPanelNode {
var dismissedPanelFrame = dismissedImportStatusPanelNode.frame
dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height
transition.updateFrame(node: dismissedImportStatusPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedImportStatusPanelNode] _ in
dismissedImportStatusPanelNode?.removeFromSupernode()
})
}
if let dismissedAdPanelNode {
var dismissedPanelFrame = dismissedAdPanelNode.frame
dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height
transition.updateAlpha(node: dismissedAdPanelNode, alpha: 0.0)
transition.updateFrame(node: dismissedAdPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedAdPanelNode] _ in
dismissedAdPanelNode?.removeFromSupernode()
})
}
if let dismissedFeePanelNode {
var dismissedPanelFrame = dismissedFeePanelNode.frame
transition.updateSublayerTransformOffset(layer: dismissedFeePanelNode.layer, offset: CGPoint(x: 0.0, y: -dismissedPanelFrame.height))
dismissedPanelFrame.origin.y = feePanelBaseY
dismissedFeePanelNode.clipsToBounds = true
dismissedPanelFrame.size.height = 0.0
if transition.isAnimated {
dismissedFeePanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
transition.updateFrame(node: dismissedFeePanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedFeePanelNode] _ in
dismissedFeePanelNode?.removeFromSupernode()
})
}
if let inputPanelNode = self.inputPanelNode, let apparentInputPanelFrame = apparentInputPanelFrame, !inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
if immediatelyLayoutInputPanelAndAnimateAppearance {
inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY)
@@ -3485,22 +3462,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.updatePlainInputSeparator(transition: .immediate)
self.backgroundNode.updateBubbleTheme(bubbleTheme: chatPresentationInterfaceState.theme, bubbleCorners: chatPresentationInterfaceState.bubbleCorners)
if self.backgroundNode.hasExtraBubbleBackground() {
if self.navigationBarBackgroundContent == nil {
if let navigationBarBackgroundContent = self.backgroundNode.makeBubbleBackground(for: .free) {
self.navigationBarBackgroundContent = navigationBarBackgroundContent
navigationBarBackgroundContent.allowsGroupOpacity = true
navigationBarBackgroundContent.implicitContentUpdate = false
navigationBarBackgroundContent.alpha = 0.3
self.navigationBar?.insertSubnode(navigationBarBackgroundContent, at: 1)
}
}
} else {
self.navigationBarBackgroundContent?.removeFromSupernode()
self.navigationBarBackgroundContent = nil
}
}
let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || chatPresentationInterfaceState.interfaceState.editMessage != nil
@@ -4445,11 +4406,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
if !found {
if !found, let controller = self.controller {
let authorName: String = (replyMessage.author.flatMap(EnginePeer.init))?.compactDisplayTitle ?? ""
let errorTextData = self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedText(authorName)
let errorText = errorTextData.string
self.controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.context.sharedContext.currentPresentationData.with({ $0 })), title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedTitle, text: errorText, actions: [
controller.present(textAlertController(context: self.context, title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedTitle, text: errorText, actions: [
TextAlertAction(type: .genericAction, title: self.chatPresentationInterfaceState.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedActionEdit, action: { [weak self] in
guard let self, let controller = self.controller else {
@@ -4909,11 +4870,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
titleViewSnapshotState: ChatTitleView.SnapshotState?,
avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState?
) -> SnapshotState {
var titleAccessoryPanelSnapshot: UIView?
if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let snapshot = titleAccessoryPanelNode.view.snapshotView(afterScreenUpdates: false) {
snapshot.frame = titleAccessoryPanelNode.frame
titleAccessoryPanelSnapshot = snapshot
}
let titleAccessoryPanelSnapshot: UIView? = nil
var inputPanelNodeSnapshot: UIView?
if let inputPanelNode = self.inputPanelNode, let snapshot = inputPanelNode.view.snapshotView(afterScreenUpdates: false) {
snapshot.frame = inputPanelNode.frame
@@ -4944,25 +4901,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let titleAccessoryPanelSnapshot = snapshotState.titleAccessoryPanelSnapshot {
self.titleAccessoryPanelContainer.view.addSubview(titleAccessoryPanelSnapshot)
if let _ = self.titleAccessoryPanelNode {
titleAccessoryPanelSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak titleAccessoryPanelSnapshot] _ in
titleAccessoryPanelSnapshot?.removeFromSuperview()
})
titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -10.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
} else {
titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -titleAccessoryPanelSnapshot.bounds.height), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak titleAccessoryPanelSnapshot] _ in
titleAccessoryPanelSnapshot?.removeFromSuperview()
})
}
}
if let titleAccessoryPanelNode = self.titleAccessoryPanelNode {
if let _ = snapshotState.titleAccessoryPanelSnapshot {
titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: true)
} else {
titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -titleAccessoryPanelNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
}
titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -titleAccessoryPanelSnapshot.bounds.height), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak titleAccessoryPanelSnapshot] _ in
titleAccessoryPanelSnapshot?.removeFromSuperview()
})
}
if let navigationBar = self.navigationBar {
@@ -1013,14 +1013,14 @@ extension ChatControllerImpl {
} else {
text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, presentCantSendMultipleFiles: {
guard let strongSelf = self else {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, presentJpegConversionAlert: { completion in
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: {
completion(false)
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.MediaPicker_ConvertToJpeg, action: {
completion(true)
@@ -1463,7 +1463,7 @@ extension ChatControllerImpl {
text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, presentSchedulePicker: { [weak self] media, done in
if let strongSelf = self {
strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in
@@ -1588,19 +1588,19 @@ extension ChatControllerImpl {
switch itemType {
case .image:
if bannedSendPhotos != nil {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return false
}
case .video:
if bannedSendVideos != nil {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return false
}
case .gif:
if bannedSendGifs != nil {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return false
}
@@ -20,6 +20,7 @@ import ChatMessageItemCommon
import ChatMessageItemView
import ReactionSelectionNode
import AnimatedTextComponent
import PresentationDataUtils
extension ChatControllerImpl {
func presentTagPremiumPaywall() {
@@ -392,7 +393,7 @@ extension ChatControllerImpl {
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})
]), in: .window(.root))
}
@@ -37,11 +37,11 @@ func chatHistoryEntriesForView(
currentState: ChatHistoryEntriesForViewState,
context: AccountContext,
location: ChatLocation,
subject: ChatControllerSubject?,
view: MessageHistoryView,
includeUnreadEntry: Bool,
includeEmptyEntry: Bool,
includeChatInfoEntry: Bool,
includeSearchEntry: Bool,
includeEmbeddedSavedChatInfo: Bool,
reverse: Bool,
groupMessages: Bool,
@@ -332,7 +332,10 @@ func chatHistoryEntriesForView(
}
}
var attributes = attributes
attributes.displayContinueThreadFooter = true
if let subject, case .pinnedMessages = subject {
} else {
attributes.displayContinueThreadFooter = true
}
entries[i] = .MessageEntry(message, presentationData, isRead, location, selection, attributes)
break outer
default:
@@ -508,7 +511,12 @@ func chatHistoryEntriesForView(
entries.insert(.ChatInfoEntry(.botInfo(title: "", text: presentationData.strings.VerificationCodes_DescriptionText, photo: nil, video: nil), presentationData), at: 0)
} else if let cachedPeerData = cachedPeerData as? CachedUserData {
if let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
entries.insert(.ChatInfoEntry(.botInfo(title: presentationData.strings.Bot_DescriptionTitle, text: botInfo.description, photo: botInfo.photo, video: botInfo.video), presentationData), at: 0)
if location.threadId == nil {
if let subject, case .pinnedMessages = subject {
} else {
entries.insert(.ChatInfoEntry(.botInfo(title: presentationData.strings.Bot_DescriptionTitle, text: botInfo.description, photo: botInfo.photo, video: botInfo.video), presentationData), at: 0)
}
}
} else if let peerStatusSettings = cachedPeerData.peerStatusSettings, peerStatusSettings.registrationDate != nil || peerStatusSettings.phoneCountry != nil {
if peerStatusSettings.flags.contains(.canAddContact) || peerStatusSettings.flags.contains(.canReport) || peerStatusSettings.flags.contains(.canBlock) {
@@ -680,15 +688,12 @@ func chatHistoryEntriesForView(
entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)))
}
}
} else if includeSearchEntry {
if view.laterId == nil {
if !view.entries.isEmpty {
entries.append(.SearchEntry(presentationData.theme.theme, presentationData.strings))
}
}
}
if addBotForumHeader {
entries.append(.ChatInfoEntry(.newThreadInfo, presentationData))
if let subject, case .pinnedMessages = subject {
} else {
entries.append(.ChatInfoEntry(.newThreadInfo, presentationData))
}
}
if includeEmbeddedSavedChatInfo, let peerId = location.peerId {
if !view.isLoading && view.laterId == nil {
@@ -11,9 +11,9 @@ import TelegramUIPreferences
import MediaResources
import AccountContext
import TemporaryCachedPeerDataManager
import ChatListSearchItemNode
import Emoji
import AppBundle
import ItemListUI
import ListMessageItem
import AccountContext
import ChatInterfaceState
@@ -216,7 +216,7 @@ extension ListMessageItemInteraction {
}
}
private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] {
private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionInsertEntry], systemStyle: ItemListSystemStyle) -> [ListViewInsertItem] {
var disableFloatingDateHeaders = false
if case .customChatContents = chatLocation {
disableFloatingDateHeaders = true
@@ -229,7 +229,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
switch mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
@@ -239,7 +239,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
}
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder)
item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .MessageGroupEntry(_, messages, presentationData):
@@ -249,7 +249,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders)
case .list:
assertionFailure()
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .UnreadEntry(_, presentationData):
@@ -267,15 +267,11 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatNewThreadInfoItem(controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .SearchEntry(theme, strings):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch()
}), directionHint: entry.directionHint)
}
}
}
private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionUpdateEntry], systemStyle: ItemListSystemStyle) -> [ListViewUpdateItem] {
var disableFloatingDateHeaders = false
if case .customChatContents = chatLocation {
disableFloatingDateHeaders = true
@@ -288,7 +284,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
switch mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
@@ -298,7 +294,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
}
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder)
item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .MessageGroupEntry(_, messages, presentationData):
@@ -308,7 +304,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders)
case .list:
assertionFailure()
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .UnreadEntry(_, presentationData):
@@ -326,16 +322,12 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatNewThreadInfoItem(controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .SearchEntry(theme, strings):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch()
}), directionHint: entry.directionHint)
}
}
}
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter)
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition, systemStyle: ItemListSystemStyle) -> ChatHistoryListViewTransition {
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.insertEntries, systemStyle: systemStyle), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.updateEntries, systemStyle: systemStyle), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter)
}
final class ChatHistoryTransactionOpaqueState {
@@ -475,6 +467,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
static let fixedAdMessageStableId: UInt32 = UInt32.max - 5000
public let context: AccountContext
private let systemStyle: ItemListSystemStyle
private(set) var chatLocation: ChatLocation
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
private let source: ChatHistoryListSource
@@ -762,7 +755,23 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
private let initTimestamp: Double
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, adMessagesContext: AdMessagesHistoryContext?, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, isChatPreview: Bool, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) {
public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>),
systemStyle: ItemListSystemStyle = .legacy,
chatLocation: ChatLocation,
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>,
adMessagesContext: AdMessagesHistoryContext?,
tag: HistoryViewInputTag?,
source: ChatHistoryListSource,
subject: ChatControllerSubject?,
controllerInteraction: ChatControllerInteraction,
selectedMessages: Signal<Set<MessageId>?, NoError>,
mode: ChatHistoryListMode = .bubbles,
rotated: Bool = false,
isChatPreview: Bool,
messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?
) {
self.initTimestamp = CFAbsoluteTimeGetCurrent()
var tag = tag
@@ -771,6 +780,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
self.context = context
self.systemStyle = systemStyle
self.chatLocation = chatLocation
self.chatLocationContextHolder = chatLocationContextHolder
self.source = source
@@ -915,7 +925,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
self.experimentalSnapScrollToItem = false
//self.debugInfo = true
@@ -1321,6 +1330,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let messageTransitionNode = self.messageTransitionNode
let mode = self.mode
let rotated = self.rotated
let systemStyle = self.systemStyle
var resetScrollingMessageId: (index: MessageIndex, offset: CGFloat)?
@@ -1801,7 +1811,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
return historyViewUpdateValue
}
}
let startTime = CFAbsoluteTimeGetCurrent()
var measure_isFirstTime = true
let messageViewQueue = Queue.mainQueue()
@@ -1931,7 +1941,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let forceSynchronous = true
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: nil, scrollAnimationCurve: nil, initialData: initialData?.initialData, keyboardButtonsMessage: nil, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: false, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: false)
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, isSavedMusic: isSavedMusic, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, isSavedMusic: isSavedMusic, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition, systemStyle: systemStyle)
if disableAnimations {
mappedTransition.options.remove(.AnimateInsertion)
@@ -2022,14 +2032,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
var reverse = false
var reverseGroups = false
var includeSearchEntry = false
if case let .list(search, reverseValue, reverseGroupsValue, _, _, _) = mode {
includeSearchEntry = search
if case let .list(reverseValue, reverseGroupsValue, _, _, _) = mode {
reverse = reverseValue
reverseGroups = reverseGroupsValue
}
var isPremium = false
if case let .user(user) = accountPeer, user.isPremium {
isPremium = true
@@ -2083,11 +2090,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
currentState: previousChatHistoryEntriesForViewState,
context: context,
location: chatLocation,
subject: subject,
view: view,
includeUnreadEntry: mode == .bubbles,
includeEmptyEntry: mode == .bubbles && tag == nil,
includeChatInfoEntry: mode == .bubbles,
includeSearchEntry: includeSearchEntry && tag != nil,
includeEmbeddedSavedChatInfo: includeEmbeddedSavedChatInfo,
reverse: reverse,
groupMessages: mode == .bubbles,
@@ -2317,12 +2324,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
var keyboardButtonsMessage = view.topTaggedMessages.first
if keyboardButtonsMessage != nil && keyboardButtonsMessage?.threadId != chatLocation.threadId {
keyboardButtonsMessage = nil
}
if let keyboardButtonsMessageValue = keyboardButtonsMessage, keyboardButtonsMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) {
keyboardButtonsMessage = nil
}
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: !isSavedMusic || forceUpdateAll)
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition, systemStyle: systemStyle)
if disableAnimations {
mappedTransition.options.remove(.AnimateInsertion)
@@ -2447,7 +2457,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let chatPresentationData = ChatPresentationData(theme: themeData, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners, animatedEmojiScale: animatedEmojiConfig.scale)
strongSelf.currentPresentationData = chatPresentationData
strongSelf.dynamicBounceEnabled = false
strongSelf.forEachItemHeaderNode { itemHeaderNode in
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNodeImpl {
@@ -4551,7 +4560,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
@@ -4561,7 +4570,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
}
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
@@ -4582,7 +4591,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders)
case .list:
assertionFailure()
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
@@ -4629,7 +4638,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
@@ -4639,7 +4648,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
}
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
@@ -20,21 +20,21 @@ enum ChatHistoryNavigationButtonType {
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
let containerNode: ContextExtractedContentContainingNode
let buttonNode: HighlightTrackingButtonNode
private let backgroundView: GlassBackgroundView
let imageView: GlassBackgroundView.ContentImageView
private let badgeBackgroundView: GlassBackgroundView
private let badgeTextNode: ImmediateAnimatedCountLabelNode
private var tapRecognizer: UITapGestureRecognizer?
var tapped: (() -> Void)? {
didSet {
if (oldValue != nil) != (self.tapped != nil) {
if self.tapped != nil {
self.buttonNode.addTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside)
} else {
self.buttonNode.removeTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside)
}
}
self.tapRecognizer?.isEnabled = self.tapped != nil && self.isEnabled
}
}
var isEnabled: Bool = true {
didSet {
self.tapRecognizer?.isEnabled = self.tapped != nil && self.isEnabled
}
}
@@ -54,7 +54,6 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.type = type
self.containerNode = ContextExtractedContentContainingNode()
self.buttonNode = HighlightTrackingButtonNode()
self.backgroundView = GlassBackgroundView()
@@ -80,7 +79,10 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
super.init()
self.targetNodeForActivationProgress = self.buttonNode
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))
self.tapRecognizer = tapRecognizer
self.backgroundView.contentView.addGestureRecognizer(tapRecognizer)
tapRecognizer.isEnabled = false
self.addSubnode(self.containerNode)
@@ -89,18 +91,16 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size)
self.containerNode.contentRect = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
self.containerNode.contentNode.addSubnode(self.buttonNode)
self.containerNode.contentNode.view.addSubview(self.backgroundView)
self.buttonNode.view.addSubview(self.backgroundView)
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate)
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: .immediate)
self.imageView.tintColor = theme.chat.inputPanel.panelControlColor
self.backgroundView.contentView.addSubview(self.imageView)
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
self.buttonNode.view.addSubview(self.badgeBackgroundView)
self.containerNode.contentNode.view.addSubview(self.badgeBackgroundView)
self.badgeBackgroundView.contentView.addSubview(self.badgeTextNode.view)
self.frame = CGRect(origin: CGPoint(), size: size)
@@ -143,9 +143,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.absoluteRect = (rect, containerSize)
}
@objc func onTap() {
if let tapped = self.tapped {
tapped()
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if let tapped = self.tapped {
tapped()
}
}
}
@@ -183,7 +183,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
if let down = self.directionButtonState.down {
self.downButton.imageView.alpha = down.isEnabled ? 1.0 : 0.5
self.downButton.buttonNode.isEnabled = down.isEnabled
self.downButton.isEnabled = down.isEnabled
mentionsOffset += buttonSize.height + 12.0
upOffset += buttonSize.height + 12.0
@@ -203,7 +203,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
if let up = self.directionButtonState.up {
self.upButton.imageView.alpha = up.isEnabled ? 1.0 : 0.5
self.upButton.buttonNode.isEnabled = up.isEnabled
self.upButton.isEnabled = up.isEnabled
mentionsOffset += buttonSize.height + 12.0
@@ -153,35 +153,8 @@ func textInputContextPanel(context: AccountContext, chatPresentationInterfaceSta
} else {
return nil
}
case let .contextRequestResult(_, results):
let _ = results
case .contextRequestResult:
return nil
/*if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) {
switch results.presentation {
case .list:
if let currentPanel = currentPanel as? VerticalListContextResultsChatInputContextPanelNode {
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
}
case .media:
if let currentPanel = currentPanel as? HorizontalListContextResultsChatInputContextPanelNode {
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
}
}
} else {
return nil
}*/
}
}
@@ -18,6 +18,7 @@ import Display
import Markdown
import TextFormat
import TelegramPresentationData
import AlertComponent
func textInputAccessoryPanel(
context: AccountContext,
@@ -112,7 +113,6 @@ func textInputAccessoryPanel(
let theme = chatPresentationInterfaceState.theme
let strings = chatPresentationInterfaceState.strings
let nameDisplayOrder = chatPresentationInterfaceState.nameDisplayOrder
let fontSize = chatPresentationInterfaceState.fontSize
return AnyComponentWithIdentity(id: "forward", component: AnyComponent(ChatInputMessageAccessoryPanel(
context: context,
@@ -158,23 +158,45 @@ func textInputAccessoryPanel(
string = strings.Conversation_ForwardOptions_Text(messages, peerDisplayTitle)
}
let font = Font.regular(floor(fontSize.baseDisplaySize * 15.0 / 17.0))
let boldFont = Font.semibold(floor(fontSize.baseDisplaySize * 15.0 / 17.0))
let body = MarkdownAttributeSet(font: font, textColor: theme.actionSheet.secondaryTextColor)
let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.actionSheet.secondaryTextColor)
let font = Font.regular(15.0)
let boldFont = Font.semibold(15.0)
let body = MarkdownAttributeSet(font: font, textColor: theme.actionSheet.primaryTextColor)
let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.actionSheet.primaryTextColor)
let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .natural)
let title = NSAttributedString(string: strings.Conversation_ForwardOptions_Title(messageCount), font: Font.semibold(floor(fontSize.baseDisplaySize)), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .center)
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(
title: strings.Conversation_ForwardOptions_Title(messageCount)
)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .attributed(text))
)
))
let alertController = richTextAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: strings.Conversation_ForwardOptions_ShowOptions, action: {
guard let sourceView else {
return
}
interfaceInteraction?.presentForwardOptions(sourceView)
let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start()
}), TextAlertAction(type: .destructiveAction, title: strings.Conversation_ForwardOptions_CancelForwarding, action: {
interfaceInteraction?.dismissForwardMessages()
})], actionLayout: .vertical)
let alertController = AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
content: content,
actions: [
.init(title: strings.Conversation_ForwardOptions_ShowOptions, action: {
guard let sourceView else {
return
}
interfaceInteraction?.presentForwardOptions(sourceView)
let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start()
}),
.init(title: strings.Conversation_ForwardOptions_CancelForwarding, type: .destructive, action: {
interfaceInteraction?.dismissForwardMessages()
})
]
)
interfaceInteraction?.presentController(alertController, nil)
}
}
@@ -37,6 +37,10 @@ import ChatMessageItemView
import ChatMessageBubbleItemNode
import AdsInfoScreen
import AdsReportScreen
import LegacyComponents
import LocalMediaResources
import MediaResources
import AVFoundation
private struct MessageContextMenuData {
let starStatus: Bool?
@@ -647,8 +651,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
var translateToLang: String?
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled {
translateToLang = translationState.toLang
}
if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) {
storeMessageTextInPasteboard(summary.text, entities: summary.entities)
} else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(message.text, entities: messageEntities)
@@ -1178,8 +1187,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
c?.dismiss(completion: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controllerInteraction.presentController(standardTextAlertController(
theme: AlertControllerTheme(presentationData: presentationData),
controllerInteraction.presentController(textAlertController(
context: context,
title: presentationData.strings.Chat_ScheduledForceSendProcessingVideo_Title,
text: presentationData.strings.Chat_ScheduledForceSendProcessingVideo_Text,
actions: [
@@ -1282,11 +1291,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
var translateToLang: String?
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled {
translateToLang = translationState.toLang
}
if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) {
storeMessageTextInPasteboard(summary.text, entities: summary.entities)
} else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(messageText, entities: messageEntities)
storeMessageTextInPasteboard(message.text, entities: messageEntities)
}
}
@@ -1355,10 +1369,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
var text = messageText
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
var translateToLang: String?
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled {
translateToLang = translationState.toLang
}
if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) {
text = summary.text
} else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
text = translation.text
}
controllerInteraction.performTextSelectionAction(message, !isCopyProtected, NSAttributedString(string: text), .speak)
f(.default)
})))
@@ -1392,6 +1413,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})
f(.default)
})))
// MARK: - Ghostgram: Send as Circle (Video Message)
if isVideo, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile {
actions.append(.action(ContextMenuActionItem(text: "Отправить как кружок", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoMessage"), color: theme.actionSheet.primaryTextColor)
}, action: { (controller: ContextControllerProtocol?, dismiss: @escaping (ContextMenuActionResult) -> Void) in
dismiss(.default)
convertVideoToCircleAndSend(context: context, message: message, file: file, controllerInteraction: controllerInteraction)
})))
}
}
}
@@ -1426,16 +1457,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if (loggingSettings.logToFile || loggingSettings.logToConsole) && !downloadableMediaResourceInfos.isEmpty {
actions.append(.action(ContextMenuActionItem(text: "Send Logs", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
triggerDebugSendLogsUI(context: context, additionalInfo: "User has requested download logs for \(downloadableMediaResourceInfos)", pushController: { c in
controllerInteraction.navigationController()?.pushViewController(c)
})
f(.default)
})))
}
// REMOVED: Send Logs button
var threadId: Int64?
var threadMessageCount: Int = 0
@@ -1511,6 +1534,56 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})))
}
// MARK: - Ghostgram: Local Edit (Client-Side Only)
// Allows editing any message text locally without sending to server
if !message.text.isEmpty {
actions.append(.action(ContextMenuActionItem(text: "Изменить локально", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak controllerInteraction] _, f in
f(.dismissWithoutContent)
guard let controllerInteraction = controllerInteraction else { return }
let peerId = message.id.peerId.toInt64()
let messageId = message.id.id
let currentText = LocalEditManager.shared.getLocalEdit(peerId: peerId, messageId: messageId) ?? message.text
let alertController = UIAlertController(
title: "Изменить локально",
message: "Это изменение видно только вам и сбросится после перезапуска приложения",
preferredStyle: .alert
)
alertController.addTextField { textField in
textField.text = currentText
textField.placeholder = "Введите новый текст"
}
alertController.addAction(UIAlertAction(title: "Отмена", style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Сохранить", style: .default) { [weak alertController] _ in
guard let textField = alertController?.textFields?.first,
let newText = textField.text, !newText.isEmpty else {
return
}
LocalEditManager.shared.setLocalEdit(peerId: peerId, messageId: messageId, newText: newText)
// Request UI update
controllerInteraction.requestMessageUpdate(message.id, true)
})
if let chatControllerNode = controllerInteraction.chatControllerNode() {
if let viewController = chatControllerNode.view.window?.rootViewController {
var topController = viewController
while let presented = topController.presentedViewController {
topController = presented
}
topController.present(alertController, animated: true, completion: nil)
}
}
})))
}
if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.isMonoForum {
var canSuggestPost = true
for media in message.media {
@@ -2012,6 +2085,33 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0)
}
// GHOSTGRAM: Show edit history button if message was edited and has saved history
if isEdited && EditHistoryManager.shared.hasEditHistory(peerId: message.id.peerId.toInt64(), messageId: message.id.id) {
actions.append(.action(ContextMenuActionItem(text: "История", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { c, f in
let editHistory = EditHistoryManager.shared.getEditHistory(peerId: message.id.peerId.toInt64(), messageId: message.id.id)
var historyText = "Оригинальные версии:\n\n"
for (index, record) in editHistory.enumerated() {
let date = Date(timeIntervalSince1970: TimeInterval(record.editDate))
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
historyText += "\(index + 1). [\(formatter.string(from: date))]\n\(record.text)\n\n"
}
c?.dismiss(completion: {
let alertController = textAlertController(
context: context,
title: "История правок",
text: historyText,
actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]
)
controllerInteraction.presentController(alertController, nil)
})
})))
}
if canViewAuthor {
actions.insert(.custom(ChatMessageAuthorContextItem(context: context, message: message, action: { c, f, peer in
@@ -2162,8 +2262,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
var translateToLang: String?
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled {
translateToLang = translationState.toLang
}
if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) {
storeMessageTextInPasteboard(summary.text, entities: summary.entities)
} else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(message.text, entities: messageEntities)
@@ -2331,7 +2436,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
}
}
if message.isCopyProtected() || message.containsSecretMedia {
if (message.isCopyProtected() || message.containsSecretMedia) && !MiscSettingsManager.shared.shouldBypassCopyProtection {
isCopyProtected = true
}
for media in message.media {
@@ -2437,7 +2542,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
}
}
if !message.containsSecretMedia && !isAction && !isShareProtected {
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.isCopyProtected() {
let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !copyProtectedCheck {
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
@@ -2453,7 +2559,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
}
} else if let group = peer as? TelegramGroup {
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia {
if !isAction && !message.isCopyProtected() && !isShareProtected {
let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection
if !isAction && !copyProtectedCheck && !isShareProtected {
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
@@ -2472,7 +2579,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
optionsMap[id]!.insert(.report)
}
} else if let user = peer as? TelegramUser {
if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !message.isCopyProtected() && !isShareProtected {
let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection
if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !copyProtectedCheck && !isShareProtected {
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
@@ -3860,3 +3968,222 @@ private final class ChatRateTranscriptionContextItemNode: ASDisplayNode, Context
return self
}
}
// MARK: - Ghostgram: Video to Circle Conversion Helper
private func convertVideoToCircleAndSend(
context: AccountContext,
message: Message,
file: TelegramMediaFile,
controllerInteraction: ChatControllerInteraction
) {
// Show progress
controllerInteraction.displayUndo(.info(title: nil, text: "Конвертация в кружок...", timeout: 5.0, customUndoText: nil))
// Get video file path
let resourcePath = context.account.postbox.mediaBox.completedResourcePath(file.resource)
guard let videoPath = resourcePath, FileManager.default.fileExists(atPath: videoPath) else {
controllerInteraction.displayUndo(.info(title: "Ошибка", text: "Видео ещё не загружено. Откройте его для загрузки.", timeout: nil, customUndoText: nil))
return
}
// Get video dimensions
var videoDimensions = CGSize(width: 240, height: 240)
for attr in file.attributes {
switch attr {
case let .Video(_, size, _, _, _, _):
videoDimensions = size.cgSize
default:
break
}
}
// Create square crop (center crop to 1:1 aspect ratio)
let minDim = min(videoDimensions.width, videoDimensions.height)
let cropX = (videoDimensions.width - minDim) / 2
let cropY = (videoDimensions.height - minDim) / 2
let cropRect = CGRect(x: cropX, y: cropY, width: minDim, height: minDim)
// Create adjustments for video message format
let adjustments = TGVideoEditAdjustments(
originalSize: videoDimensions,
cropRect: cropRect,
cropOrientation: .up,
cropRotation: 0.0,
cropLockedAspectRatio: 1.0,
cropMirrored: false,
trimStartValue: 0.0,
trimEndValue: 0.0,
toolValues: nil,
paintingData: nil,
sendAsGif: false,
preset: TGMediaVideoConversionPresetVideoMessage
)
// Generate output path
let outputPath = NSTemporaryDirectory() + "circle_\(Int64.random(in: Int64.min...Int64.max)).mp4"
print("[Ghostgram Circle] Starting conversion from: \(videoPath)")
print("[Ghostgram Circle] Output path: \(outputPath)")
print("[Ghostgram Circle] Video dimensions: \(videoDimensions)")
// Start conversion in background
DispatchQueue.global(qos: .userInitiated).async {
// AVFoundation needs file extension to detect format
// Postbox stores files without extension, so copy to temp with .mp4
let tempSourcePath = NSTemporaryDirectory() + "source_\(Int64.random(in: Int64.min...Int64.max)).mp4"
do {
try FileManager.default.copyItem(atPath: videoPath, toPath: tempSourcePath)
print("[Ghostgram Circle] Copied to temp with extension: \(tempSourcePath)")
} catch {
print("[Ghostgram Circle] ERROR: Failed to copy file: \(error)")
DispatchQueue.main.async {
controllerInteraction.displayUndo(.info(title: "Ошибка", text: "Не удалось подготовить видео", timeout: nil, customUndoText: nil))
}
return
}
let videoUrl = URL(fileURLWithPath: tempSourcePath)
let avAsset = AVURLAsset(url: videoUrl)
print("[Ghostgram Circle] Created AVAsset, starting conversion...")
// Use TGMediaVideoConverter with correct signature
guard let signal = TGMediaVideoConverter.convert(
avAsset,
adjustments: adjustments,
path: outputPath,
watcher: nil,
entityRenderer: nil
) else {
print("[Ghostgram Circle] ERROR: Failed to create conversion signal!")
DispatchQueue.main.async {
controllerInteraction.displayUndo(.info(title: "Ошибка", text: "Не удалось запустить конвертацию", timeout: nil, customUndoText: nil))
}
return
}
print("[Ghostgram Circle] Conversion signal created, starting...")
// Use SBlockDisposable instead to keep signal alive
let disposable = signal.start(next: { result in
print("[Ghostgram Circle] Got result: \(String(describing: result))")
guard let result = result as? TGMediaVideoConversionResult,
let resultUrl = result.fileURL else {
print("[Ghostgram Circle] ERROR: Result is not TGMediaVideoConversionResult or no fileURL")
DispatchQueue.main.async {
controllerInteraction.displayUndo(.info(title: "Ошибка", text: "Не удалось конвертировать видео", timeout: nil, customUndoText: nil))
}
return
}
print("[Ghostgram Circle] Conversion SUCCESS! File: \(resultUrl.path)")
let finalDuration = result.duration
let finalDimensions = result.dimensions
DispatchQueue.main.async {
// Create preview image
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let previewImage = result.coverImage {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min...Int64.max))
let thumbnailSize = CGSize(width: 240, height: 240)
if let thumbnailImage = TGScaleImageToPixelSize(previewImage, thumbnailSize),
let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData)
previewRepresentations.append(TelegramMediaImageRepresentation(
dimensions: PixelDimensions(thumbnailSize),
resource: resource,
progressiveSizes: [],
immediateThumbnailData: nil,
hasVideo: false,
isPersonal: false
))
}
}
// Create local resource for converted video
let videoResource = LocalFileVideoMediaResource(
randomId: Int64.random(in: Int64.min...Int64.max),
path: resultUrl.path,
adjustments: nil
)
// Create TelegramMediaFile with instantRoundVideo flag
let media = TelegramMediaFile(
fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min...Int64.max)),
partialReference: nil,
resource: videoResource,
previewRepresentations: previewRepresentations,
videoThumbnails: [],
immediateThumbnailData: nil,
mimeType: "video/mp4",
size: nil,
attributes: [
.FileName(fileName: "video.mp4"),
.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil)
],
alternativeRepresentations: []
)
// Create message
let outMessage: EnqueueMessage = .message(
text: "",
attributes: [],
inlineStickers: [:],
mediaReference: .standalone(media: media),
threadId: nil,
replyToMessageId: nil,
replyToStoryId: nil,
localGroupingKey: nil,
correlationId: nil,
bubbleUpEmojiOrStickersets: []
)
print("[Ghostgram Circle] Sending message...")
// Send message
let _ = enqueueMessages(account: context.account, peerId: message.id.peerId, messages: [outMessage]).start()
// Show success
controllerInteraction.displayUndo(.succeed(text: "Кружок отправлен!", timeout: nil, customUndoText: nil))
}
}, error: { error in
print("[Ghostgram Circle] ERROR in conversion: \(String(describing: error))")
DispatchQueue.main.async {
controllerInteraction.displayUndo(.info(title: "Ошибка", text: "Не удалось конвертировать видео", timeout: nil, customUndoText: nil))
}
}, completed: {
print("[Ghostgram Circle] Conversion completed callback!")
})
// Keep disposable alive - hold reference until task completes
// This is a workaround for ObjC signal disposal
DispatchQueue.main.async {
CircleConversionHolder.shared.add(disposable)
}
}
}
// Helper class to hold conversion disposables
private final class CircleConversionHolder {
static let shared = CircleConversionHolder()
private var disposables: [AnyObject] = []
private let lock = NSLock()
func add(_ disposable: AnyObject?) {
guard let disposable = disposable else { return }
lock.lock()
disposables.append(disposable)
lock.unlock()
// Auto-cleanup after 2 minutes
DispatchQueue.main.asyncAfter(deadline: .now() + 120) { [weak self] in
self?.lock.lock()
self?.disposables.removeAll { $0 === disposable }
self?.lock.unlock()
}
}
}
@@ -6,6 +6,7 @@ import ChatPresentationInterfaceState
import ChatControllerInteraction
import ComponentFlow
import ChatSideTopicsPanel
import LegacyChatHeaderPanelComponent
func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTitleAccessoryPanelNode? {
if !force, case .standard(.embedded) = chatPresentationInterfaceState.mode {
@@ -11,6 +11,7 @@ import TelegramNotices
import AnimatedAvatarSetNode
import AccountContext
import ChatPresentationInterfaceState
import LegacyChatHeaderPanelComponent
private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode {
private var theme: PresentationTheme?
@@ -14,6 +14,7 @@ import TelegramCore
import BundleIconComponent
import ContextUI
import SwiftSignalKit
import LegacyChatHeaderPanelComponent
private final class ChatManagingBotTitlePanelComponent: Component {
let context: AccountContext
@@ -933,7 +933,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
}
if itemNode == nil || itemNode === currentItemNode {
if let contextController = self.contextController {
contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition)
contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
}
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
standaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
@@ -23,6 +23,7 @@ import AnimationCache
import MultiAnimationRenderer
import TranslateUI
import ChatControllerInteraction
import LegacyChatHeaderPanelComponent
private enum PinnedMessageAnimation {
case slideToTop
@@ -67,8 +68,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private let imageNode: TransformImageNode
private let imageNodeContainer: ASDisplayNode
private let separatorNode: ASDisplayNode
private var currentLayout: (CGFloat, CGFloat, CGFloat)?
private var currentMessage: ChatPinnedMessage?
private var previousMediaReference: AnyMediaReference?
@@ -132,9 +131,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.activityIndicator.alpha = 0.0
ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 0.1)
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
self.contextContainer = ContextControllerSourceNode()
self.clippingContainer = ASDisplayNode()
@@ -219,8 +215,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
self.contextContainer.addSubnode(self.tapButton)
self.addSubnode(self.separatorNode)
self.contextContainer.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
@@ -249,9 +243,19 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
if self.theme !== interfaceState.theme {
themeUpdated = true
self.theme = interfaceState.theme
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: [])
self.listButton.setImage(PresentationResourcesChat.chatInputPanelPinnedListIconImage(interfaceState.theme), for: [])
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(interfaceState.theme.chat.inputPanel.panelControlColor.cgColor)
context.setLineWidth(1.33)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
}), for: [])
self.listButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/PinnedList"), color: interfaceState.theme.chat.inputPanel.panelControlColor), for: [])
self.actionButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 14.0 * 2.0, color: interfaceState.theme.list.itemCheckColors.fillColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil)
}
@@ -473,7 +477,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
transition.updateFrame(node: self.activityIndicatorContainer, frame: CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width + 5.0, y: 15.0), size: indicatorSize))
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(), size: indicatorSize))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - tapButtonRightInset, height: panelHeight))
self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
@@ -534,7 +537,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 0.1)
if let theme = self.theme {
self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {
self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {
})
}
}
@@ -609,20 +612,20 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
var titleStrings: [AnimatedCountLabelNode.Segment] = []
if let _ = giveaway {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
} else {
if pinnedMessage.totalCount == 2 {
if pinnedMessage.index == 0 {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
} else {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
}
} else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
} else {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor)))
}
}
@@ -675,14 +678,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
} else {
titleString = ""
}
titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))]
} else {
for media in message.media {
if let media = media as? TelegramMediaInvoice {
titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))]
break
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))]
break
}
}
@@ -810,7 +813,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight)))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 6.0), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
strongSelf.textNode.textNode.frame = textFrame
@@ -857,8 +860,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame)
strongSelf.lineNode.update(
colors: AnimatedNavigationStripeNode.Colors(
foreground: theme.chat.inputPanel.panelControlAccentColor,
background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5),
foreground: theme.chat.inputPanel.panelControlColor,
background: theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.5),
clearBackground: theme.chat.inputPanel.panelBackgroundColor
),
configuration: AnimatedNavigationStripeNode.Configuration(
@@ -869,7 +872,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
transition: animationTransition
)
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0))
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0))
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
if let applyImage = applyImage {
@@ -16,6 +16,7 @@ import AnimationCache
import MultiAnimationRenderer
import AccountContext
import PremiumUI
import LegacyChatHeaderPanelComponent
private enum ChatReportPeerTitleButton: Equatable {
case block
@@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit
import TelegramPresentationData
import ChatPresentationInterfaceState
import LegacyChatHeaderPanelComponent
final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode {
private let separatorNode: ASDisplayNode
@@ -157,7 +157,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.tintSubtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: .black)
let panelHeight = defaultHeight(metrics: metrics)
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 16.0 * 2.0, height: panelHeight))
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
var originX: CGFloat = leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0)
@@ -196,7 +196,8 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
combinedFrame = combinedFrame.union(iconView.frame)
}
combinedFrame = combinedFrame.insetBy(dx: -12.0, dy: -6.0)
combinedFrame.origin.y += 1.0
combinedFrame.size.height = 40.0
combinedFrame.origin.y = floorToScreenPixels((panelHeight - 40.0) * 0.5)
self.textNode.frame = textFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY)
self.tintTextNode.frame = self.textNode.frame
@@ -291,7 +291,6 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@@ -43,7 +43,7 @@ final class ChatSearchResultsController: ViewController {
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.presentationData = presentationData
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationData.theme, presentationStrings: presentationData.strings))
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationData.theme, presentationStrings: presentationData.strings), transition: .immediate)
strongSelf.controllerNode.updatePresentationData(presentationData)
}
})
@@ -17,6 +17,7 @@ import ContextUI
import PromptUI
import BundleIconComponent
import SavedTagNameAlertController
import LegacyChatHeaderPanelComponent
private let backgroundTagImage: UIImage? = {
if let image = UIImage(bundleImageName: "Chat/Title Panels/SearchTagTab") {
@@ -557,7 +558,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat
self.update(params: params, transition: transition)
}
let panelHeight: CGFloat = 39.0
let panelHeight: CGFloat = 40.0
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0)
}
@@ -567,7 +568,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat
}
private func update(params: Params, transition: ContainedViewLayoutTransition) {
let panelHeight: CGFloat = 39.0
let panelHeight: CGFloat = 40.0
let containerInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 16.0, bottom: 0.0, right: params.rightInset + 16.0)
let itemSpacing: CGFloat = 24.0
@@ -598,7 +599,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat
}
let itemSize = promoView.update(theme: params.interfaceState.theme, strings: params.interfaceState.strings, height: panelHeight, isUnlock: !self.items.isEmpty, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: 0.0), size: itemSize)
itemTransition.updatePosition(layer: promoView.layer, position: itemFrame.center)
promoView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
@@ -704,7 +705,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat
}
}
let itemSize = itemView.update(item: item, isSelected: isSelected, isLocked: !params.interfaceState.isPremium, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize)
let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: 0.0), size: itemSize)
itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center)
itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
@@ -59,6 +59,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
private let backgroundContainerView: GlassBackgroundContainerView
private let leftControlsBackgroundView: GlassBackgroundView
private let rightControlsBackgroundView: GlassBackgroundView
private let calendarButton = ComponentView<Empty>()
@@ -93,13 +94,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
init(theme: PresentationTheme, alwaysShowTotalMessagesCount: Bool) {
self.alwaysShowTotalMessagesCount = alwaysShowTotalMessagesCount
self.backgroundContainerView = GlassBackgroundContainerView()
self.leftControlsBackgroundView = GlassBackgroundView()
self.rightControlsBackgroundView = GlassBackgroundView()
super.init()
self.view.addSubview(self.leftControlsBackgroundView)
self.view.addSubview(self.rightControlsBackgroundView)
self.view.addSubview(self.backgroundContainerView)
self.backgroundContainerView.contentView.addSubview(self.leftControlsBackgroundView)
self.backgroundContainerView.contentView.addSubview(self.rightControlsBackgroundView)
}
deinit {
@@ -108,6 +111,14 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
var leftInset = leftInset + 8.0
var rightInset = rightInset + 8.0
if bottomInset <= 32.0 {
leftInset += 18.0
rightInset += 18.0
}
let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let currentLayout = self.currentLayout, currentLayout.params == params {
return currentLayout.height
@@ -332,7 +343,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
buttonView.alpha = 0.0
self.view.addSubview(buttonView)
}
let listModeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 20.0 - 8.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)
let listModeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 8.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)
listModeButtonFrameValue = listModeFrame
listModeButtonTransition.setPosition(view: buttonView, position: CGPoint(x: listModeFrame.minX + listModeFrame.width * buttonView.layer.anchorPoint.x, y: listModeFrame.minY + listModeFrame.height * buttonView.layer.anchorPoint.y))
listModeButtonTransition.setBounds(view: buttonView, bounds: CGRect(origin: CGPoint(), size: listModeFrame.size))
@@ -349,7 +360,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
var nextLeftX: CGFloat = 16.0 + 8.0
var nextLeftX: CGFloat = params.leftInset + 4.0
var calendarButtonFrameValue: CGRect?
var membersButtonFrameValue: CGRect?
@@ -547,7 +558,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
var leftControlsBackgroundFrame = CGRect(origin: CGPoint(x: 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
var leftControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.leftInset, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
leftControlsBackgroundFrame.size.width = max(40.0, leftControlsRect.maxX - leftControlsBackgroundFrame.minX)
transition.setFrame(view: self.leftControlsBackgroundView, frame: leftControlsBackgroundFrame)
self.leftControlsBackgroundView.update(size: leftControlsBackgroundFrame.size, cornerRadius: leftControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
@@ -568,12 +579,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
}
}
var rightControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
var rightControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0))
rightControlsBackgroundFrame.size.width = max(40.0, rightControlsRect.maxX - rightControlsRect.minX + 8.0 * 2.0)
rightControlsBackgroundFrame.origin.x -= rightControlsBackgroundFrame.width
transition.setFrame(view: self.rightControlsBackgroundView, frame: rightControlsBackgroundFrame)
self.rightControlsBackgroundView.update(size: rightControlsBackgroundFrame.size, cornerRadius: rightControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
transition.setAlpha(view: self.rightControlsBackgroundView, alpha: rightControlsRect.isEmpty ? 0.0 : 1.0)
self.rightControlsBackgroundView.isHidden = rightControlsRect.isEmpty
transition.setFrame(view: self.backgroundContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: height)))
self.backgroundContainerView.update(size: CGSize(width: params.width, height: height), isDark: params.interfaceState.theme.overallDarkAppearance, transition: transition)
return height
}
@@ -3,6 +3,7 @@ import UIKit
import Display
import AsyncDisplayKit
import ChatPresentationInterfaceState
import LegacyChatHeaderPanelComponent
final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode {
private let separatorNode: ASDisplayNode
@@ -15,6 +15,7 @@ import AnimationCache
import MultiAnimationRenderer
import AccountContext
import TelegramNotices
import LegacyChatHeaderPanelComponent
final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
private let context: AccountContext
@@ -175,8 +175,6 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in
@@ -107,7 +107,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.button]
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.separatorNode)
@@ -144,7 +144,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.button]
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.shadowNode)
@@ -41,8 +41,9 @@ public class ComposeControllerImpl: ViewController, ComposeController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
self._hasGlassStyle = true
self.navigationPresentation = .modal
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
@@ -51,8 +52,6 @@ public class ComposeControllerImpl: ViewController, ComposeController {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
self.scrollToTop = { [weak self] in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
@@ -93,7 +92,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.presentationData.strings.Compose_NewMessage
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@@ -131,7 +131,7 @@ final class ComposeControllerNode: ASDisplayNode {
self.requestOpenDisabledPeerFromSearch?(peer, reason)
}, contextAction: nil), cancel: { [weak self] in
self?.requestDeactivateSearch?()
})
}, fieldStyle: placeholderNode.fieldStyle)
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
@@ -109,7 +109,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
self.titleView = CounterControllerTitleView(theme: self.presentationData.theme)
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
self._hasGlassStyle = true
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
@@ -226,7 +227,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.updateTitle()
self.contactsNode.updatePresentationData(self.presentationData)
@@ -277,7 +277,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
}
}
self.tokenListNode = EditableTokenListNode(context: self.context, presentationTheme: self.presentationData.theme, theme: EditableTokenListNodeTheme(backgroundColor: .clear, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, tokenBackgroundColor: self.presentationData.theme.list.itemCheckColors.strokeColor.withAlphaComponent(0.25), selectedTextColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, selectedBackgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.rootController.keyboardColor), placeholder: placeholder, shortPlaceholder: shortPlaceholder)
self.tokenListNode = EditableTokenListNode(context: self.context, theme: self.presentationData.theme, placeholder: placeholder, shortPlaceholder: shortPlaceholder)
super.init()
@@ -413,6 +413,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
insets.top += strongSelf.tokenListNode.bounds.size.height
insets.top += 10.0 + 10.0
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
@@ -465,16 +466,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
insets.top += 10.0
let tokenListHeight = self.tokenListNode.updateLayout(tokens: self.editableTokens, width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
let tokenListHeight = self.tokenListNode.updateLayout(tokens: self.editableTokens, width: layout.size.width - (16.0 + layout.safeInsets.left) * 2.0, leftInset: 0.0, rightInset: 0.0, transition: transition)
transition.updateFrame(node: self.tokenListNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: tokenListHeight)))
transition.updateFrame(node: self.tokenListNode, frame: CGRect(origin: CGPoint(x: 16.0 + layout.safeInsets.left, y: insets.top), size: CGSize(width: layout.size.width - (16.0 + layout.safeInsets.left) * 2.0, height: tokenListHeight)))
var headerInsets = layout.insets(options: [.input])
headerInsets.top += actualNavigationBarHeight
insets.top += tokenListHeight
headerInsets.top += tokenListHeight
headerInsets.top += 10.0 + tokenListHeight + 8.0
insets = headerInsets
if let footerPanelNode = self.footerPanelNode {
var count = 0
@@ -133,7 +133,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground())
}
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self._hasGlassStyle = true
self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true
@@ -236,18 +238,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
guard case .glass = self.style else {
return
}
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let barButtonSize = CGSize(width: 44.0, height: 44.0)
let closeComponent: AnyComponentWithIdentity<Empty> = AnyComponentWithIdentity(
id: "close",
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: nil,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .generic,
animateScale: false,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor
tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
@@ -262,13 +265,14 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
id: "search",
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: nil,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .generic,
animateScale: false,
component: AnyComponentWithIdentity(id: "search", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Search",
tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor
tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
@@ -289,15 +293,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self.closeButtonNode = closeButtonNode
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode)
}
let searchButtonNode: BarComponentHostNode
if let current = self.searchButtonNode {
searchButtonNode = current
searchButtonNode.component = searchComponent
if searchComponent != nil {
let searchButtonNode: BarComponentHostNode
if let current = self.searchButtonNode {
searchButtonNode = current
searchButtonNode.component = searchComponent
} else {
searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize)
self.searchButtonNode = searchButtonNode
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode)
}
} else {
searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize)
self.searchButtonNode = searchButtonNode
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode)
self.navigationItem.rightBarButtonItem = nil
}
}
@@ -307,7 +315,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
if case .glass = self.style {
glass = true
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), transition: .immediate)
(self.searchContentNode as? NavigationBarSearchContentNode)?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
self.title = self.titleProducer(self.presentationData.strings)
self.tabBarItem.title = self.presentationData.strings.Contacts_Title
@@ -571,7 +579,7 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode {
init(presentationData: PresentationData, dismissSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void) {
self.presentationData = presentationData
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern)
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings, fieldStyle: .modern)
self.searchBar.placeholderString = NSAttributedString(string: presentationData.strings.Common_Search, font: searchBarFont, textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
super.init()
@@ -591,10 +599,12 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode {
return 56.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
return size
}
func activate() {
@@ -615,7 +625,7 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode {
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings)
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings)
}
}
@@ -477,7 +477,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()
}
})
}, fieldStyle: placeholderNode.fieldStyle)
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
@@ -194,7 +194,7 @@ private enum CreateChannelEntry: ItemListNodeEntry {
let arguments = arguments as! CreateChannelArguments
switch self {
case let .channelInfo(_, _, dateTimeFormat, peer, state, avatar):
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
arguments.updateEditingName(editingName)
}, editingNameCompleted: {
arguments.focusOnDescription()
@@ -202,11 +202,11 @@ private enum CreateChannelEntry: ItemListNodeEntry {
arguments.changeProfilePhoto()
}, updatingImage: avatar, tag: CreateChannelEntryTag.info)
case let .setProfilePhoto(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.changeProfilePhoto()
})
case let .descriptionSetup(_, text, value):
return ItemListMultilineInputItem(presentationData: presentationData, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
return ItemListMultilineInputItem(presentationData: presentationData, systemStyle: .glass, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
arguments.updateEditingDescriptionText(updatedText)
}, tag: CreateChannelEntryTag.description)
case let .descriptionInfo(_, text):
@@ -214,7 +214,7 @@ private enum CreateChannelEntry: ItemListNodeEntry {
case let .usernameHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .username(theme, placeholder, text):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(updatedText)
}, action: {
})
@@ -321,7 +321,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
let arguments = arguments as! CreateGroupArguments
switch self {
case let .groupInfo(_, _, dateTimeFormat, peer, state, avatar):
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
arguments.updateEditingName(editingName)
}, editingNameCompleted: {
arguments.done()
@@ -329,13 +329,13 @@ private enum CreateGroupEntry: ItemListNodeEntry {
arguments.changeProfilePhoto()
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
case let .setProfilePhoto(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.changeProfilePhoto()
})
case let .usernameHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .username(theme, placeholder, text):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(updatedText)
}, action: {
})
@@ -364,24 +364,24 @@ private enum CreateGroupEntry: ItemListNodeEntry {
case let .usernameInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .topics(_, text):
return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in })
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in })
case let .topicsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .autoDelete(text, value):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
arguments.updateAutoDelete()
}, tag: CreateGroupEntryTag.autoDelete)
case let .autoDeleteInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .member(_, _, _, dateTimeFormat, nameDisplayOrder, peer, presence):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .locationHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .location(theme, location):
let imageSignal = chatMapSnapshotImage(engine: arguments.context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
return ItemListAddressItem(theme: theme, systemStyle: .glass, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
case let .changeLocation(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.changeLocation()
})
case let .locationInfo(_, text):
@@ -389,7 +389,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
case let .venueHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .venue(_, _, venue):
return ItemListVenueItem(presentationData: presentationData, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: {
return ItemListVenueItem(presentationData: presentationData, systemStyle: .glass, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: {
arguments.updateWithVenue(venue)
})
}
@@ -100,7 +100,7 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode {
self.symbolNode = TextNode()
self.symbolNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.symbolNode)
}
@@ -176,7 +176,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
peer: peer,
title: self.strings.Chat_HashtagSuggestion_UseLocal_Title("#\(query)@\(addressName)").string,
text: isGroup ? self.strings.Chat_HashtagSuggestion_UseLocal_Group_Text : self.strings.Chat_HashtagSuggestion_UseLocal_Channel_Text,
badge: self.strings.ChatList_ContextMenuBadgeNew,
badge: nil,
hashtag: "\(query)@\(addressName)",
revealed: false,
isAdditionalRecent: false
@@ -149,7 +149,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.button]
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.separatorNode)
self.addSubnode(self.titleNode)
@@ -133,7 +133,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
CMTimebaseSetRate(timebase!, rate: 0.0)
self.timebase = timebase!
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.imageNodeBackground)
@@ -126,7 +126,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
self.activateAreaNode = AccessibilityAreaNode()
self.activateAreaNode.accessibilityTraits = [.button]
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.separatorNode)
@@ -95,10 +95,7 @@ final class NotificationContainerControllerNode: ASDisplayNode {
})
}
var useCompactLayout = false
if let validLayout = self.validLayout {
useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0
}
let useCompactLayout = "".isEmpty
let itemNode = item.node(compact: useCompactLayout)
let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode)
@@ -165,10 +162,7 @@ final class NotificationContainerControllerNode: ASDisplayNode {
}
self.topItemAndNode = nil
var useCompactLayout = false
if let validLayout = self.validLayout {
useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0
}
let useCompactLayout = "".isEmpty
let itemNode = item.node(compact: useCompactLayout)
let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode)
@@ -2,14 +2,16 @@ import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramPresentationData
import ChatMessageNotificationItem
import GlassBackgroundComponent
final class NotificationItemContainerNode: ASDisplayNode {
private let backgroundNode: ASImageNode
private var validLayout: ContainerViewLayout?
private let theme: PresentationTheme
private let backgroundView = GlassBackgroundView()
var item: NotificationItem?
private var hapticFeedback: HapticFeedback?
@@ -40,6 +42,8 @@ final class NotificationItemContainerNode: ASDisplayNode {
}
}
private var validLayout: ContainerViewLayout?
var dismissed: ((NotificationItem) -> Void)?
var cancelTimeout: ((NotificationItem) -> Void)?
var resumeTimeout: ((NotificationItem) -> Void)?
@@ -48,15 +52,10 @@ final class NotificationItemContainerNode: ASDisplayNode {
init(theme: PresentationTheme, contentNode: NotificationItemNode?) {
self.contentNode = contentNode
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = PresentationResourcesRootController.inAppNotificationBackground(theme)
self.theme = theme
super.init()
self.addSubnode(self.backgroundNode)
if let contentNode {
self.addSubnode(contentNode)
}
@@ -65,6 +64,8 @@ final class NotificationItemContainerNode: ASDisplayNode {
override func didLoad() {
super.didLoad()
self.view.insertSubview(self.backgroundView, at: 0)
if let contentNode = self.contentNode, !contentNode.acceptsTouches {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
@@ -76,13 +77,13 @@ final class NotificationItemContainerNode: ASDisplayNode {
func animateIn() {
if let _ = self.validLayout {
self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), to: CGPoint(), duration: 0.4, additive: true)
self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.backgroundView.frame.maxY), to: CGPoint(), duration: 0.4, additive: true)
}
}
func animateOut(completion: @escaping () -> Void) {
if let _ = self.validLayout {
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), duration: 0.4, removeOnCompletion: false, additive: true, completion: { _ in
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.backgroundView.frame.maxY), duration: 0.4, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
} else {
@@ -100,7 +101,7 @@ final class NotificationItemContainerNode: ASDisplayNode {
if let statusBarHeight = layout.statusBarHeight, statusBarHeight >= 39.0 {
if layout.deviceMetrics.hasDynamicIsland {
contentInsets.top = statusBarHeight
contentInsets.top = statusBarHeight + 6.0
} else if statusBarHeight >= 44.0 {
contentInsets.top += 34.0
} else {
@@ -113,7 +114,10 @@ final class NotificationItemContainerNode: ASDisplayNode {
let contentWidth = containerWidth - contentInsets.left - contentInsets.right
let contentHeight = contentNode.updateLayout(width: contentWidth, transition: transition)
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - containerWidth - 8.0 * 2.0) / 2.0), y: contentInsets.top - 16.0), size: CGSize(width: containerWidth + 8.0 * 2.0, height: 8.0 + contentHeight + 20.0 + 8.0)))
let backgroundInset: CGFloat = 8.0
let backgroundSize = CGSize(width: containerWidth - backgroundInset * 2.0, height: contentHeight)
self.backgroundView.update(size: backgroundSize, cornerRadius: 24.0, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition))
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - backgroundSize.width) / 2.0), y: contentInsets.top), size: backgroundSize))
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - contentWidth) / 2.0), y: contentInsets.top), size: CGSize(width: contentWidth, height: contentHeight)))
}
@@ -38,6 +38,7 @@ import TextFormat
import BrowserUI
import MediaEditorScreen
import GiftSetupScreen
import AlertComponent
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation {
@@ -95,7 +96,7 @@ func openResolvedUrlImpl(
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
case .inaccessiblePeer:
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
case let .botStart(peer, payload):
openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)))
case let .groupBotStart(botPeerId, payload, adminRights, peerType):
@@ -132,9 +133,8 @@ func openResolvedUrlImpl(
let addMemberImpl = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = AlertControllerTheme(presentationData: presentationData)
let attributedTitle = NSAttributedString(string: presentationData.strings.Bot_AddToChat_Add_MemberAlertTitle, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center)
let strings = presentationData.strings
var isGroup: Bool = false
var peerTitle: String = ""
if case let .legacyGroup(peer) = peer {
@@ -147,51 +147,54 @@ func openResolvedUrlImpl(
peerTitle = peer.title
}
let text = isGroup ? presentationData.strings.Bot_AddToChat_Add_MemberAlertTextGroup(peerTitle).string : presentationData.strings.Bot_AddToChat_Add_MemberAlertTextChannel(peerTitle).string
let text = isGroup ? strings.Bot_AddToChat_Add_MemberAlertTextGroup(peerTitle).string : strings.Bot_AddToChat_Add_MemberAlertTextChannel(peerTitle).string
let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Bot_AddToChat_Add_MemberAlertAdd, action: {
if payload.isEmpty {
if peerId.namespace == Namespaces.Peer.CloudGroup {
let _ = (context.engine.peers.addGroupMember(peerId: peerId, memberId: botPeerId)
|> deliverOnMainQueue).startStandalone(completed: {
controller?.dismiss()
})
} else {
let _ = (context.engine.peers.addChannelMember(peerId: peerId, memberId: botPeerId)
|> deliverOnMainQueue).startStandalone(completed: {
controller?.dismiss()
})
}
} else {
let _ = (context.engine.messages.requestStartBotInGroup(botPeerId: botPeerId, groupPeerId: peerId, payload: payload)
|> deliverOnMainQueue).startStandalone(next: { result in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer = peer else {
return
let alertController = textAlertController(
context: context,
title: strings.Bot_AddToChat_Add_MemberAlertTitle,
text: text,
actions: [
TextAlertAction(type: .defaultAction, title: strings.Bot_AddToChat_Add_MemberAlertAdd, action: {
if payload.isEmpty {
if peerId.namespace == Namespaces.Peer.CloudGroup {
let _ = (context.engine.peers.addGroupMember(peerId: peerId, memberId: botPeerId)
|> deliverOnMainQueue).startStandalone(completed: {
controller?.dismiss()
})
} else {
let _ = (context.engine.peers.addChannelMember(peerId: peerId, memberId: botPeerId)
|> deliverOnMainQueue).startStandalone(completed: {
controller?.dismiss()
})
}
if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
}
switch result {
case let .channelParticipant(participant):
context.peerChannelMemberCategoriesContextsManager.externallyAdded(peerId: peerId, participant: participant)
case .none:
break
}
controller?.dismiss()
})
}, error: { _ in
})
}
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})], actionLayout: .vertical)
present(controller, nil)
} else {
let _ = (context.engine.messages.requestStartBotInGroup(botPeerId: botPeerId, groupPeerId: peerId, payload: payload)
|> deliverOnMainQueue).startStandalone(next: { result in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer = peer else {
return
}
if let navigationController = navigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
}
switch result {
case let .channelParticipant(participant):
context.peerChannelMemberCategoriesContextsManager.externallyAdded(peerId: peerId, participant: participant)
case .none:
break
}
controller?.dismiss()
})
}, error: { _ in
})
}
}),
TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {})
]
)
present(alertController, nil)
}
if case let .channel(peer) = peer {
@@ -1340,15 +1343,21 @@ func openResolvedUrlImpl(
storyProgressPauseContext.update(controller)
}
} else {
let controller = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Chat_ErrorCantBoost, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
present(controller, nil)
controller.dismissed = { _ in
let alertController = AlertScreen(
context: context,
title: nil,
text: presentationData.strings.Chat_ErrorCantBoost,
actions: [
.init(title: presentationData.strings.Common_OK, type: .default)
]
)
alertController.dismissed = { _ in
dismissedImpl?()
}
present(alertController, nil)
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
storyProgressPauseContext.update(controller)
storyProgressPauseContext.update(alertController)
}
}
case let .premiumGiftCode(slug):
@@ -1515,6 +1524,7 @@ func openResolvedUrlImpl(
let controller = context.sharedContext.makeGiftAuctionViewScreen(
context: context,
auctionContext: auctionContext,
peerId: nil,
completion: { [weak navigationController] acquiredGifts, upgradeAttributes in
if let upgradeAttributes {
let controller = context.sharedContext.makeGiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, acquiredGifts: acquiredGifts, attributes: upgradeAttributes, completion: {
+11 -3
View File
@@ -561,9 +561,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
}
}
if isToken {
context.sharedContext.presentGlobalController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
}),
context.sharedContext.presentGlobalController(textAlertController(context: context, title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}),
], parseMarkdown: true), nil)
return
}
@@ -770,6 +768,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
var startApp: String?
var text: String?
var profile: Bool = false
var direct: Bool = false
var referrer: String?
var albumId: Int64?
var collectionId: Int64?
@@ -823,6 +822,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
startChannel = ""
} else if queryItem.name == "profile" {
profile = true
} else if queryItem.name == "direct" {
direct = true
} else if queryItem.name == "startapp" {
startApp = ""
}
@@ -918,6 +919,13 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
convertedUrl = current + "?profile"
}
}
if direct, let current = convertedUrl {
if current.contains("?") {
convertedUrl = current + "&direct"
} else {
convertedUrl = current + "?direct"
}
}
}
} else if parsedUrl.host == "hostOverride" {
if let components = URLComponents(string: "/?" + query) {
@@ -259,7 +259,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
self.historyNode.clipsToBounds = true
super.init()
@@ -881,7 +881,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
historyNode.clipsToBounds = true
historyNode.preloadPages = true
historyNode.stackFromBottom = true
@@ -10,6 +10,7 @@ import TelegramCallsUI
import TelegramUIPreferences
import AccountContext
import DeviceLocationManager
import ItemListUI
import LegacyUI
import ChatListUI
import PeersNearbyUI
@@ -92,6 +93,8 @@ import AttachmentFileController
import NewContactScreen
import PasskeysScreen
import GiftDemoScreen
import ChatTextLinkEditUI
import CocoonInfoScreen
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@@ -2242,6 +2245,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return ChatHistoryListNodeImpl(
context: context,
updatedPresentationData: updatedPresentationData,
systemStyle: .glass,
chatLocation: chatLocation,
chatLocationContextHolder: chatLocationContextHolder,
adMessagesContext: nil,
@@ -3191,6 +3195,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else {
return
}
controller.view.window?.endEditing(true)
var dismissAlertImpl: (() -> Void)?
let alertController = giftTransferAlertController(
context: context,
@@ -3317,7 +3323,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
controller.present(alertController, in: .current)
dismissAlertImpl = { [weak alertController] in
alertController?.dismissAnimated()
alertController?.dismiss()
}
}
@@ -3713,7 +3719,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
}
public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) {
return avatarMediaPickerController(context: context, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed)
}
@@ -3857,20 +3863,20 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftAuctionBidScreen(context: context, toPeerId: toPeerId, text: text, entities: entities, hideName: hideName, auctionContext: auctionContext, acquiredGifts: acquiredGifts)
}
public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController {
return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: completion)
public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController {
return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, peerId: peerId, completion: completion)
}
public func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController {
return GiftAuctionActiveBidsScreen(context: context)
}
public func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController {
return giftOfferAlertController(context: context, gift: gift, peer: peer, amount: amount, commit: commit)
public func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController {
return giftOfferAlertController(context: context, updatedPresentationData: updatedPresentationData, gift: gift, peer: peer, amount: amount, commit: commit)
}
public func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController {
return GiftUpgradePreviewScreen(context: context, gift: gift, attributes: attributes)
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
return GiftUpgradeVariantsScreen(context: context, gift: gift, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
}
public func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController {
@@ -4023,6 +4029,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return SendInviteLinkScreen(context: context, subject: subject, peers: peers, theme: theme)
}
public func makeCocoonInfoScreen(context: AccountContext) -> ViewController {
return CocoonInfoScreen(context: context)
}
public func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController {
return chatTextLinkEditController(context: context, updatedPresentationData: updatedPresentationData, text: text, link: link, apply: apply)
}
@available(iOS 13.0, *)
public func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController {
return await PostSuggestionsSettingsScreen(context: context, peerId: peerId, completion: {})
@@ -31,7 +31,6 @@ import PeerInfoScreen
import PeerInfoStoryGridScreen
import ShareWithPeersScreen
import ChatEmptyNode
import UndoUI
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
private var presentationData: PresentationData
@@ -42,7 +41,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh
init(context: AccountContext) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: true)
self.emptyNode = ChatEmptyNode(context: context, interaction: nil)
@@ -55,7 +54,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false)
}
@@ -188,7 +187,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
public func addRootControllers(showCallsTab: Bool) {
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme)
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme, strings: self.presentationData.strings)
tabBarController.navigationPresentation = .master
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
@@ -96,7 +96,7 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem
self.titleNode = TextNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.separatorNode)
@@ -118,7 +118,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
self.iconImageNode.isLayerBacked = !smartInvertColorsEnabled()
self.iconImageNode.displaysAsynchronously = false
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.separatorNode)