Pre-release improvements

This commit is contained in:
Ali 2022-04-01 17:25:59 +04:00
parent eb9ff7beda
commit e93bc2efab
18 changed files with 363 additions and 187 deletions

View File

@ -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<Data?, NoError> = .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<Int>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int(Int32.max), MediaBoxFetchPriority.maximum)])
fetchNotificationSoundSignal = Signal { subscriber in
let collectedData = Atomic<Data>(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(

View File

@ -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";

View File

@ -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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -214,10 +214,25 @@ private func apiInputPeerNotifySettings(_ settings: MessageNotificationSettings)
private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal<Void, NoError> {
let pushedChats = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyChats, settings: apiInputPeerNotifySettings(settings.groupChats)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
let pushedUsers = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyUsers, settings: apiInputPeerNotifySettings(settings.privateChats)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
let pushedChannels = network.request(Api.functions.account.updateNotifySettings(peer: Api.InputNotifyPeer.inputNotifyBroadcasts, settings: apiInputPeerNotifySettings(settings.channels)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
let pushedContactsJoined = network.request(Api.functions.account.setContactSignUpNotification(silent: settings.contactsJoined ? .boolFalse : .boolTrue))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
return combineLatest(pushedChats, pushedUsers, pushedChannels, pushedContactsJoined)
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in return .complete() }
}

View File

@ -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<Void, NoError> 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<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { result -> Signal<Void, NoError> 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) {

View File

@ -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

View File

@ -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)))
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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:

View File

@ -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
}

View File

@ -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,

View File

@ -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