From e93bc2efab5b40a581a6666cda6892c0008870d6 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 1 Apr 2022 17:25:59 +0400 Subject: [PATCH] Pre-release improvements --- .../Sources/NotificationService.swift | 109 ++++++++++++++-- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Sources/AccountContext.swift | 4 +- .../Sources/Items/ItemListCheckboxItem.swift | 19 ++- .../Sources/NotificationSoundSelection.swift | 16 ++- submodules/Postbox/Sources/MediaBox.swift | 6 +- ...ificationExceptionSettingsController.swift | 2 +- .../ManagedGlobalNotificationSettings.swift | 17 ++- ...nagedPendingPeerNotificationSettings.swift | 16 ++- .../Peers/NotificationSoundList.swift | 2 +- .../Sources/NumericFormat.swift | 8 +- .../Sources/MessageContentKind.swift | 6 +- .../TelegramUI/Sources/ChatController.swift | 67 +++++----- .../ChatInterfaceStateContextMenus.swift | 121 +++++++++--------- .../TelegramUI/Sources/ChatTimerScreen.swift | 64 ++++----- .../Sources/NavigateToChatController.swift | 1 + .../Sources/PeerInfo/PeerInfoData.swift | 10 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 81 ++++++------ 18 files changed, 363 insertions(+), 187 deletions(-) diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index fdc0406745..91ad35039d 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -722,9 +722,15 @@ private final class NotificationServiceHandler { return } strongSelf.stateManager = stateManager + + let settings = stateManager.postbox.transaction { transaction -> NotificationSoundList? in + return _internal_cachedNotificationSoundList(transaction: transaction) + } - strongSelf.notificationKeyDisposable.set((existingMasterNotificationsKey(postbox: stateManager.postbox) - |> deliverOn(strongSelf.queue)).start(next: { notificationsKey in + strongSelf.notificationKeyDisposable.set((combineLatest(queue: strongSelf.queue, + existingMasterNotificationsKey(postbox: stateManager.postbox), + settings + ) |> deliverOn(strongSelf.queue)).start(next: { notificationsKey, notificationSoundList in guard let strongSelf = self else { let content = NotificationContent(isLockedMessage: nil) updateCurrentContent(content) @@ -765,6 +771,7 @@ private final class NotificationServiceHandler { var peerId: PeerId? var messageId: MessageId.Id? var mediaAttachment: Media? + var downloadNotificationSound: (file: TelegramMediaFile, path: String, fileName: String)? var interactionAuthorId: PeerId? @@ -925,12 +932,31 @@ private final class NotificationServiceHandler { content.threadId = threadId } - if let sound = aps["sound"] as? String { - if let customSoundPath = customSoundPath { - content.sound = customSoundPath - } else { - content.sound = sound + if let ringtoneString = aps["ringtone"] as? String, let fileId = Int64(ringtoneString) { + content.sound = "0.m4a" + if let notificationSoundList = notificationSoundList { + for sound in notificationSoundList.sounds { + if sound.file.fileId.id == fileId { + let containerSoundsPath = appGroupUrl.path + "/Library/Sounds" + let soundFileName = "\(fileId).mp3" + let soundFilePath = containerSoundsPath + "/\(soundFileName)" + + if !FileManager.default.fileExists(atPath: soundFilePath) { + let _ = try? FileManager.default.createDirectory(atPath: containerSoundsPath, withIntermediateDirectories: true, attributes: nil) + if let filePath = stateManager.postbox.mediaBox.completedResourcePath(id: sound.file.resource.id, pathExtension: nil) { + let _ = try? FileManager.default.copyItem(atPath: filePath, toPath: soundFilePath) + } else { + downloadNotificationSound = (sound.file, soundFilePath, soundFileName) + } + } + + content.sound = soundFileName + break + } + } } + } else if let sound = aps["sound"] as? String { + content.sound = sound } if let category = aps["category"] as? String { @@ -1014,7 +1040,6 @@ private final class NotificationServiceHandler { queue.async { guard let strongSelf = self, let stateManager = strongSelf.stateManager else { - let content = NotificationContent(isLockedMessage: isLockedMessage) updateCurrentContent(content) completed() @@ -1078,17 +1103,79 @@ private final class NotificationServiceHandler { } } } + + var fetchNotificationSoundSignal: Signal = .single(nil) + if let (downloadNotificationSound, _, _) = downloadNotificationSound { + var fetchResource: TelegramMultipartFetchableResource? + fetchResource = downloadNotificationSound.resource as? TelegramMultipartFetchableResource + + if let resource = fetchResource { + if let _ = strongSelf.stateManager?.postbox.mediaBox.completedResourcePath(resource) { + } else { + let intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int(Int32.max), MediaBoxFetchPriority.maximum)]) + fetchNotificationSoundSignal = Signal { subscriber in + let collectedData = Atomic(value: Data()) + return standaloneMultipartFetch( + postbox: stateManager.postbox, + network: stateManager.network, + resource: resource, + datacenterId: resource.datacenterId, + size: nil, + intervals: intervals, + parameters: MediaResourceFetchParameters( + tag: nil, + info: resourceFetchInfo(resource: resource), + isRandomAccessAllowed: true + ), + encryptionKey: nil, + decryptedSize: nil, + continueInBackground: false, + useMainConnection: true + ).start(next: { result in + switch result { + case let .dataPart(_, data, _, _): + let _ = collectedData.modify { current in + var current = current + current.append(data) + return current + } + default: + break + } + }, error: { _ in + subscriber.putNext(nil) + subscriber.putCompletion() + }, completed: { + subscriber.putNext(collectedData.with({ $0 })) + subscriber.putCompletion() + }) + } + } + } + } Logger.shared.log("NotificationService \(episode)", "Will fetch media") - let _ = (fetchMediaSignal - |> timeout(10.0, queue: queue, alternate: .single(nil)) - |> deliverOn(queue)).start(next: { mediaData in + let _ = (combineLatest(queue: queue, + fetchMediaSignal + |> timeout(10.0, queue: queue, alternate: .single(nil)), + fetchNotificationSoundSignal + |> timeout(10.0, queue: queue, alternate: .single(nil)) + ) + |> deliverOn(queue)).start(next: { mediaData, notificationSoundData in guard let strongSelf = self, let stateManager = strongSelf.stateManager else { completed() return } Logger.shared.log("NotificationService \(episode)", "Did fetch media \(mediaData == nil ? "Non-empty" : "Empty")") + + if let notificationSoundData = notificationSoundData { + Logger.shared.log("NotificationService \(episode)", "Did fetch notificationSoundData") + + if let (_, filePath, fileName) = downloadNotificationSound { + let _ = try? notificationSoundData.write(to: URL(fileURLWithPath: filePath)) + } + } Logger.shared.log("NotificationService \(episode)", "Will get unread count") let _ = (getCurrentRenderedTotalUnreadCount( diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 536c49be26..b0cf47ecc5 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4060,6 +4060,7 @@ Unused sets are archived when you add more."; "Undo.ChatDeleted" = "Chat deleted"; "Undo.ChatCleared" = "Chat cleared"; "Undo.ChatClearedForBothSides" = "Chat cleared for both sides"; +"Undo.ChatClearedForEveryone" = "Chat cleared for everyone"; "Undo.SecretChatDeleted" = "Secret Chat deleted"; "Undo.LeftChannel" = "Left channel"; "Undo.LeftGroup" = "Left group"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 1fd13f2ede..a46aba1fc9 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -363,9 +363,10 @@ public final class NavigateToChatControllerParams { public let chatListFilter: Int32? public let chatNavigationStack: [PeerId] public let changeColors: Bool + public let setupController: (ChatController) -> Void public let completion: (ChatController) -> Void - public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) { + public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, completion: @escaping (ChatController) -> Void = { _ in }) { self.navigationController = navigationController self.chatController = chatController self.chatLocationContextHolder = chatLocationContextHolder @@ -390,6 +391,7 @@ public final class NavigateToChatControllerParams { self.chatListFilter = chatListFilter self.chatNavigationStack = chatNavigationStack self.changeColors = changeColors + self.setupController = setupController self.completion = completion } } diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift index 86f51a4c6c..67a49c8518 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift @@ -21,6 +21,11 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem { case check } + public enum TextColor { + case primary + case accent + } + let presentationData: ItemListPresentationData let icon: UIImage? let iconSize: CGSize? @@ -28,13 +33,14 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem { let title: String let style: ItemListCheckboxItemStyle let color: ItemListCheckboxItemColor + let textColor: TextColor let checked: Bool let zeroSeparatorInsets: Bool public let sectionId: ItemListSectionId let action: () -> Void let deleteAction: (() -> Void)? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) { + public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) { self.presentationData = presentationData self.icon = icon self.iconSize = iconSize @@ -42,6 +48,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem { self.title = title self.style = style self.color = color + self.textColor = textColor self.checked = checked self.zeroSeparatorInsets = zeroSeparatorInsets self.sectionId = sectionId @@ -181,7 +188,15 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let titleColor: UIColor + switch item.textColor { + case .primary: + titleColor = item.presentationData.theme.list.itemPrimaryTextColor + case .accent: + titleColor = item.presentationData.theme.list.itemAccentColor + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index 5becf0d648..0c0fafe526 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -183,7 +183,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .uploadSound(text): let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme) - return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.upload() }) case let .cloudInfo(text): @@ -479,6 +479,20 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co return } + do { + let resources = try url.resourceValues(forKeys:[.fileSizeKey]) + if let size = resources.fileSize { + if Int32(size) > settings.maxSize { + //TODO:localize + presentUndo(.info(title: "Audio is too large", text: "The file is over \(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).")) + return + } + } + } catch { + print("Error: \(error)") + return + } + let coordinator = NSFileCoordinator(filePresenter: nil) var error: NSError? coordinator.coordinate(readingItemAt: url, options: .forUploading, error: &error, byAccessor: { url in diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 5d0d6ea91f..3284c947ea 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -425,7 +425,11 @@ public final class MediaBox { } public func completedResourcePath(_ resource: MediaResource, pathExtension: String? = nil) -> String? { - let paths = self.storePathsForId(resource.id) + return self.completedResourcePath(id: resource.id, pathExtension: pathExtension) + } + + public func completedResourcePath(id: MediaResourceId, pathExtension: String? = nil) -> String? { + let paths = self.storePathsForId(id) if let _ = fileSize(paths.complete) { self.timeBasedCleanup.touch(paths: [ paths.complete diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift index 88f6699b49..b2af7e7dbc 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift @@ -212,7 +212,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .uploadSound(_, text): let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme) - return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.upload() }) case let .displayPreviewsHeader(_, _, text): diff --git a/submodules/TelegramCore/Sources/State/ManagedGlobalNotificationSettings.swift b/submodules/TelegramCore/Sources/State/ManagedGlobalNotificationSettings.swift index 01f6814dad..567a3df1c0 100644 --- a/submodules/TelegramCore/Sources/State/ManagedGlobalNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/State/ManagedGlobalNotificationSettings.swift @@ -214,10 +214,25 @@ private func apiInputPeerNotifySettings(_ settings: MessageNotificationSettings) private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal { let pushedChats = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyChats, settings: apiInputPeerNotifySettings(settings.groupChats))) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + let pushedUsers = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyUsers, settings: apiInputPeerNotifySettings(settings.privateChats))) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + let pushedChannels = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyBroadcasts, settings: apiInputPeerNotifySettings(settings.channels))) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + let pushedContactsJoined = network.request(Api.functions.account.setContactSignUpNotification(silent: settings.contactsJoined ? .boolFalse : .boolTrue)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + return combineLatest(pushedChats, pushedUsers, pushedChannels, pushedContactsJoined) - |> retryRequest |> mapToSignal { _ -> Signal in return .complete() } } diff --git a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift index 33c196d7a9..5df8e81646 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift @@ -129,14 +129,16 @@ private func pushPeerNotificationSettings(postbox: Postbox, network: Network, pe } let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound) return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings)) - |> retryRequest - |> mapToSignal { result -> Signal in - return postbox.transaction { transaction -> Void in - transaction.updateCurrentPeerNotificationSettings([notificationPeerId: settings]) - if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) - } + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + transaction.updateCurrentPeerNotificationSettings([notificationPeerId: settings]) + if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) } + } } } else { if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationSoundList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationSoundList.swift index f2491b80ee..1c9cc843a8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationSoundList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationSoundList.swift @@ -124,7 +124,7 @@ func _internal_cachedNotificationSoundListCacheKey() -> ItemCacheEntryId { return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.notificationSoundList, key: key) } -func _internal_cachedNotificationSoundList(transaction: Transaction) -> NotificationSoundList? { +public func _internal_cachedNotificationSoundList(transaction: Transaction) -> NotificationSoundList? { let cached = transaction.retrieveItemCacheEntry(id: _internal_cachedNotificationSoundListCacheKey())?.get(NotificationSoundList.self) if let cached = cached { return cached diff --git a/submodules/TelegramPresentationData/Sources/NumericFormat.swift b/submodules/TelegramPresentationData/Sources/NumericFormat.swift index ec999b6a9e..c5d0c4c92c 100644 --- a/submodules/TelegramPresentationData/Sources/NumericFormat.swift +++ b/submodules/TelegramPresentationData/Sources/NumericFormat.swift @@ -62,8 +62,10 @@ public func timeIntervalString(strings: PresentationStrings, value: Int32, prefe return strings.MessageTimer_Days(max(1, value / (60 * 60 * 24))) } else if value <= 60 * 60 * 24 * 30 { return strings.MessageTimer_Weeks(max(1, value / (60 * 60 * 24 * 7))) - } else { + } else if value <= 60 * 60 * 24 * 365 { return strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))) + } else { + return strings.MessageTimer_Years(max(1, value / (60 * 60 * 24 * 365))) } } else { if value < 60 { @@ -76,8 +78,10 @@ public func timeIntervalString(strings: PresentationStrings, value: Int32, prefe return strings.MessageTimer_Days(max(1, value / (60 * 60 * 24))) } else if value < 60 * 60 * 24 * 30 { return strings.MessageTimer_Weeks(max(1, value / (60 * 60 * 24 * 7))) - } else { + } else if value < 60 * 60 * 24 * 365 { return strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))) + } else { + return strings.MessageTimer_Years(max(1, value / (60 * 60 * 24 * 365))) } } } diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index fb75bc4792..cc00c25f01 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -179,7 +179,11 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil case let .dice(dice): return .dice(dice.emoji) case let .invoice(invoice): - return .invoice(invoice.title) + if !invoice.description.isEmpty { + return .invoice(invoice.description) + } else { + return .invoice(invoice.title) + } default: return nil } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9dc1c58fb2..1489495bc2 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9767,6 +9767,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + func beginClearHistory(type: InteractiveHistoryClearingType) { + guard case let .peer(peerId) = self.chatLocation else { + return + } + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) + self.chatDisplayNode.historyNode.historyAppearsCleared = true + + let statusText: String + if case .scheduledMessages = self.presentationInterfaceState.subject { + statusText = self.presentationData.strings.Undo_ScheduledMessagesCleared + } else if case .forEveryone = type { + if peerId.namespace == Namespaces.Peer.CloudUser { + statusText = self.presentationData.strings.Undo_ChatClearedForBothSides + } else { + statusText = self.presentationData.strings.Undo_ChatClearedForEveryone + } + } else { + statusText = self.presentationData.strings.Undo_ChatCleared + } + + self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { [weak self] value in + guard let strongSelf = self else { + return false + } + if value == .commit { + let _ = strongSelf.context.engine.messages.clearHistoryInteractively(peerId: peerId, type: type).start(completed: { + self?.chatDisplayNode.historyNode.historyAppearsCleared = false + }) + return true + } else if value == .undo { + strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = false + return true + } + return false + }), in: .current) + } + private func navigationButtonAction(_ action: ChatNavigationButtonAction) { switch action { case .spacer: @@ -9775,36 +9812,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) case .clearHistory: if case let .peer(peerId) = self.chatLocation { - let context = self.context - let beginClear: (InteractiveHistoryClearingType) -> Void = { [weak self] type in - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true - - let statusText: String - if case .scheduledMessages = strongSelf.presentationInterfaceState.subject { - statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared - } else if case .forEveryone = type { - statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides - } else { - statusText = strongSelf.presentationData.strings.Undo_ChatCleared - } - - strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { value in - if value == .commit { - let _ = context.engine.messages.clearHistoryInteractively(peerId: peerId, type: type).start(completed: { - self?.chatDisplayNode.historyNode.historyAppearsCleared = false - }) - return true - } else if value == .undo { - self?.chatDisplayNode.historyNode.historyAppearsCleared = false - return true - } - return false - }), in: .current) + self?.beginClearHistory(type: type) } let _ = (self.context.account.postbox.transaction { transaction -> Bool in diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 2e509de832..883c3cc6f4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -681,6 +681,67 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } + for media in message.media { + if let file = media as? TelegramMediaFile, let size = file.size, let duration = file.duration, (["audio/mpeg", "audio/mp3", "audio/mpeg3"] as [String]).contains(file.mimeType.lowercased()) { + actions.append(.action(ContextMenuActionItem(text: "Save for Notifications", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.default) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 })) + if size > settings.maxSize { + //TODO:localize + controllerInteraction.displayUndo(.info(title: "Audio is too large", text: "The file is over \(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).")) + } else if Double(duration) > Double(settings.maxDuration) { + //TODO:localize + controllerInteraction.displayUndo(.info(title: "Audio is too long", text: "The duration is longer than \(stringForDuration(Int32(settings.maxDuration))).")) + } else { + let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file)) + |> deliverOnMainQueue).start(completed: { + //TODO:localize + controllerInteraction.displayUndo(.notificationSoundAdded(title: "Sound added", text: "You can now use this sound as a notification tone in your [custom notification settings]().", action: { + controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil)) + })) + }) + } + }))) + + /* + + let _ = (context.account.postbox.mediaBox.resourceData(file.resource, option: .incremental(waitUntilFetchStatus: false)) + |> take(1) + |> deliverOnMainQueue).start(next: { data in + if data.complete { + let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0] + let soundsDirectoryPath = documentsDirectoryPath + "/Sounds" + + let _ = try? FileManager.default.createDirectory(atPath: soundsDirectoryPath, withIntermediateDirectories: true, attributes: nil) + + let containerSoundsPath = context.sharedContext.applicationBindings.containerPath + "/Library/Sounds" + + let _ = try? FileManager.default.createDirectory(atPath: containerSoundsPath, withIntermediateDirectories: true, attributes: nil) + + let soundFileName = "\(UInt32.random(in: 0 ..< UInt32.max)).mp3" + let soundPath = soundsDirectoryPath + "/\(soundFileName)" + + let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: soundPath) + let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: "\(containerSoundsPath)/\(soundFileName)") + + let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in + var settings = settings + + settings.customSound = soundFileName + + return settings + }).start() + } + })*/ + } + actions.append(.separator) + } + var isReplyThreadHead = false if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId @@ -870,66 +931,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - for media in message.media { - if let file = media as? TelegramMediaFile, let size = file.size, let duration = file.duration, (["audio/mpeg", "audio/mp3", "audio/mpeg3"] as [String]).contains(file.mimeType.lowercased()) { - actions.append(.action(ContextMenuActionItem(text: "Save for Notifications", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - f(.default) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 })) - if size > settings.maxSize { - //TODO:localize - controllerInteraction.displayUndo(.info(title: "Audio is too large", text: "The file is over \(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).")) - } else if Double(duration) > Double(settings.maxDuration) { - //TODO:localize - controllerInteraction.displayUndo(.info(title: "Audio is too long", text: "The duration is longer than \(stringForDuration(Int32(settings.maxDuration))).")) - } else { - let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file)) - |> deliverOnMainQueue).start(completed: { - //TODO:localize - controllerInteraction.displayUndo(.notificationSoundAdded(title: "Sound added", text: "You can now use this sound as a notification tone in your [custom notification settings]().", action: { - controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil)) - })) - }) - } - }))) - - /* - - let _ = (context.account.postbox.mediaBox.resourceData(file.resource, option: .incremental(waitUntilFetchStatus: false)) - |> take(1) - |> deliverOnMainQueue).start(next: { data in - if data.complete { - let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0] - let soundsDirectoryPath = documentsDirectoryPath + "/Sounds" - - let _ = try? FileManager.default.createDirectory(atPath: soundsDirectoryPath, withIntermediateDirectories: true, attributes: nil) - - let containerSoundsPath = context.sharedContext.applicationBindings.containerPath + "/Library/Sounds" - - let _ = try? FileManager.default.createDirectory(atPath: containerSoundsPath, withIntermediateDirectories: true, attributes: nil) - - let soundFileName = "\(UInt32.random(in: 0 ..< UInt32.max)).mp3" - let soundPath = soundsDirectoryPath + "/\(soundFileName)" - - let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: soundPath) - let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: "\(containerSoundsPath)/\(soundFileName)") - - let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in - var settings = settings - - settings.customSound = soundFileName - - return settings - }).start() - } - })*/ - } - } - var downloadableMediaResourceInfos: [String] = [] for media in message.media { if let file = media as? TelegramMediaFile { diff --git a/submodules/TelegramUI/Sources/ChatTimerScreen.swift b/submodules/TelegramUI/Sources/ChatTimerScreen.swift index 034888d56f..d7d1ded606 100644 --- a/submodules/TelegramUI/Sources/ChatTimerScreen.swift +++ b/submodules/TelegramUI/Sources/ChatTimerScreen.swift @@ -279,6 +279,8 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi private var initialTime: Int32? private var pickerView: TimerPickerView? + private let autoremoveTimerValues: [Int32] + private var containerLayout: (ContainerViewLayout, CGFloat)? var completion: ((Int32) -> Void)? @@ -370,6 +372,25 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi break } + self.autoremoveTimerValues = [ + 1 * 24 * 60 * 60 as Int32, + 2 * 24 * 60 * 60 as Int32, + 3 * 24 * 60 * 60 as Int32, + 4 * 24 * 60 * 60 as Int32, + 5 * 24 * 60 * 60 as Int32, + 6 * 24 * 60 * 60 as Int32, + 1 * 7 * 24 * 60 * 60 as Int32, + 2 * 7 * 24 * 60 * 60 as Int32, + 3 * 7 * 24 * 60 * 60 as Int32, + 1 * 31 * 24 * 60 * 60 as Int32, + 2 * 30 * 24 * 60 * 60 as Int32, + 3 * 31 * 24 * 60 * 60 as Int32, + 4 * 30 * 24 * 60 * 60 as Int32, + 5 * 31 * 24 * 60 * 60 as Int32, + 6 * 30 * 24 * 60 * 60 as Int32, + 365 * 24 * 60 * 60 as Int32 + ] + super.init() self.backgroundColor = nil @@ -452,13 +473,14 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi self.pickerView = pickerView if let value = self.initialTime { - let days = value / (24 * 60 * 60) - let hours = (value % (24 * 60 * 60)) / (60 * 60) - let minutes = (value % (60 * 60)) / 60 + var selectedRowIndex = 0 + for i in 0 ..< self.autoremoveTimerValues.count { + if self.autoremoveTimerValues[i] <= value { + selectedRowIndex = i + } + } - pickerView.selectRow(Int(days), inComponent: 0, animated: false) - pickerView.selectRow(Int(hours), inComponent: 1, animated: false) - pickerView.selectRow(Int(minutes), inComponent: 2, animated: false) + pickerView.selectRow(selectedRowIndex, inComponent: 0, animated: false) } case .mute: let pickerView = TimerDatePickerView() @@ -487,7 +509,7 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi case .sendTimer: return 1 case .autoremove: - return 3 + return 1 case .mute: return 0 } @@ -498,16 +520,7 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi case .sendTimer: return timerValues.count case .autoremove: - switch component { - case 0: - return 365 - case 1: - return 24 - case 2: - return 60 - default: - return 0 - } + return self.autoremoveTimerValues.count case .mute: return 0 } @@ -536,19 +549,12 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi itemView.textColor = self.presentationData.theme.list.itemPrimaryTextColor } - let string: String - switch component { - case 0: - string = dayIntervalString(strings: self.presentationData.strings, days: Int32(row)) - case 1: - string = hoursIntervalString(strings: self.presentationData.strings, hours: Int32(row)) - case 2: - string = minutesIntervalString(strings: self.presentationData.strings, minutes: Int32(row)) - default: - preconditionFailure() - } + let value = self.autoremoveTimerValues[row] - itemView.value = (Int32(row), string) + let string: String + string = timeIntervalString(strings: self.presentationData.strings, value: value) + + itemView.value = (value, string) return itemView case .mute: diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 44b7d5466b..ca8dd8a28a 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -64,6 +64,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if let attachBotStart = params.attachBotStart { controller.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload) } + params.setupController(controller) found = true break } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 4585679e5b..7495366fc1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -169,6 +169,7 @@ final class TelegramGlobalSettings { final class PeerInfoScreenData { let peer: Peer? + let chatPeer: Peer? let cachedData: CachedPeerData? let status: PeerInfoStatusData? let notificationSettings: TelegramPeerNotificationSettings? @@ -186,6 +187,7 @@ final class PeerInfoScreenData { init( peer: Peer?, + chatPeer: Peer?, cachedData: CachedPeerData?, status: PeerInfoStatusData?, notificationSettings: TelegramPeerNotificationSettings?, @@ -202,6 +204,7 @@ final class PeerInfoScreenData { requestsContext: PeerInvitationImportersContext? ) { self.peer = peer + self.chatPeer = chatPeer self.cachedData = cachedData self.status = status self.notificationSettings = notificationSettings @@ -428,6 +431,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, return PeerInfoScreenData( peer: peerView.peers[peerId], + chatPeer: peerView.peers[peerId], cachedData: peerView.cachedData, status: nil, notificationSettings: nil, @@ -453,6 +457,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen case .none, .settings: return .single(PeerInfoScreenData( peer: nil, + chatPeer: nil, cachedData: nil, status: nil, notificationSettings: nil, @@ -566,7 +571,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen combinedKeys.append(.peerChatState(peerId: secretChatId)) } return combineLatest( - context.account.viewTracker.peerView(userPeerId, updateData: true), + context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId), context.account.postbox.combinedView(keys: combinedKeys), status @@ -595,6 +600,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return PeerInfoScreenData( peer: peerView.peers[userPeerId], + chatPeer: peerView.peers[peerId], cachedData: peerView.cachedData, status: status, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, @@ -680,6 +686,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return PeerInfoScreenData( peer: peerView.peers[peerId], + chatPeer: peerView.peers[peerId], cachedData: peerView.cachedData, status: status, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, @@ -858,6 +865,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return PeerInfoScreenData( peer: peerView.peers[groupId], + chatPeer: peerView.peers[groupId], cachedData: peerView.cachedData, status: status, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 095aead872..23b95eb874 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3701,7 +3701,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } case .more: - guard let data = self.data, let peer = data.peer else { + guard let data = self.data, let peer = data.peer, let chatPeer = data.chatPeer else { return } let presentationData = self.presentationData @@ -3753,10 +3753,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate var canSetupAutoremoveTimeout = false - if let secretChat = peer as? TelegramSecretChat { + if let secretChat = chatPeer as? TelegramSecretChat { currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout - canSetupAutoremoveTimeout = true - } else if let group = peer as? TelegramGroup { + canSetupAutoremoveTimeout = false + } else if let group = chatPeer as? TelegramGroup { if case .creator = group.role { canSetupAutoremoveTimeout = true } else if case let .admin(rights, _) = group.role { @@ -3764,11 +3764,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate canSetupAutoremoveTimeout = true } } - } else if let user = peer as? TelegramUser { + } else if let user = chatPeer as? TelegramUser { if user.id != strongSelf.context.account.peerId && user.botInfo == nil { canSetupAutoremoveTimeout = true } - } else if let channel = peer as? TelegramChannel { + } else if let channel = chatPeer as? TelegramChannel { if channel.hasPermission(.deleteAllMessages) { canSetupAutoremoveTimeout = true } @@ -3807,17 +3807,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate 31 * 24 * 60 * 60 ] - if let _ = currentAutoremoveTimeout { - //TODO:localize - subItems.append(.action(ContextMenuActionItem(text: "Disable", icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: nil) - }))) - } - for value in presetValues { subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in return nil @@ -3837,6 +3826,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self?.openAutoremove(currentValue: currentAutoremoveTimeout) }))) + if let _ = currentAutoremoveTimeout { + //TODO:localize + subItems.append(.action(ContextMenuActionItem(text: "Disable", textColor: .destructive, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: nil) + }))) + } + subItems.append(.separator) //TODO:localize @@ -4339,8 +4339,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }))) subItems.append(.separator) - //TODO:localize - guard let anyType = clearPeerHistory.canClearForEveryone ?? clearPeerHistory.canClearForMyself else { return } @@ -4348,8 +4346,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let title: String switch anyType { case .user, .secretChat, .savedMessages: + //TODO:localize title = "Are you sure you want to delete all messages with \(EnginePeer(chatPeer).compactDisplayTitle)?" case .group, .channel: + //TODO:localize title = "Are you sure you want to delete all messages in \(EnginePeer(chatPeer).compactDisplayTitle)?" } @@ -4361,27 +4361,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let statusText: String - if case .forEveryone = type { - statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides - } else { - statusText = strongSelf.presentationData.strings.Undo_ChatCleared - } - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { value in - if value == .commit { - guard let strongSelf = self else { - return false - } - - let _ = strongSelf.context.engine.messages.clearHistoryInteractively(peerId: peer.id, type: type).start(completed: { - }) - return true - } else if value == .undo { - return true - } - return false - }), in: .current) + strongSelf.openChatWithClearedHistory(type: type) } if let canClearForEveryone = clearPeerHistory.canClearForEveryone { @@ -4775,6 +4756,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } + private func openChatWithClearedHistory(type: InteractiveHistoryClearingType) { + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), setupController: { controller in + (controller as? ChatControllerImpl)?.beginClearHistory(type: type) + }, completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } + private func openAddContact() { let _ = (getUserPeer(postbox: self.context.account.postbox, peerId: self.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer, _ in