mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-06-08 11:03:55 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user