Notification sound updates

This commit is contained in:
Ali 2022-03-31 23:49:52 +04:00
parent ed012a56cb
commit eb9ff7beda
4 changed files with 251 additions and 128 deletions

View File

@ -108,9 +108,11 @@ public func parseMarkdownIntoAttributedString(_ string: String, attributes: Mark
} }
result.append(NSAttributedString(string: bold, attributes: boldAttributes)) result.append(NSAttributedString(string: bold, attributes: boldAttributes))
} else { } else {
if remainingRange.length != 0 {
result.append(NSAttributedString(string: nsString.substring(with: NSMakeRange(remainingRange.location, 1)), attributes: bodyAttributes)) result.append(NSAttributedString(string: nsString.substring(with: NSMakeRange(remainingRange.location, 1)), attributes: bodyAttributes))
remainingRange = NSMakeRange(range.location + 1, remainingRange.length - 1) remainingRange = NSMakeRange(range.location + 1, remainingRange.length - 1)
} }
}
} else { } else {
if result.string.hasSuffix("\\") { if result.string.hasSuffix("\\") {
result.deleteCharacters(in: NSMakeRange(result.string.count - 1, 1)) result.deleteCharacters(in: NSMakeRange(result.string.count - 1, 1))

View File

@ -451,13 +451,28 @@ public func notificationSoundSelectionController(context: AccountContext, update
controller?.dismiss() controller?.dismiss()
} }
var presentUndo: ((UndoOverlayContent) -> Void)?
presentFilePicker = { [weak controller] in presentFilePicker = { [weak controller] in
guard let controller = controller else { guard let controller = controller else {
return return
} }
presentCustomNotificationSoundFilePicker(context: context, controller: controller, disposable: soundActionDisposable)
}
return controller
}
public func presentCustomNotificationSoundFilePicker(context: AccountContext, controller: ViewController, disposable: MetaDisposable) {
let presentUndo: (UndoOverlayContent) -> Void = { [weak controller] content in
guard let controller = controller else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 }))
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller.present(legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.mp3"], completion: { urls in controller.present(legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.mp3"], completion: { urls in
guard !urls.isEmpty, let url = urls.first else { guard !urls.isEmpty, let url = urls.first else {
@ -473,9 +488,9 @@ public func notificationSoundSelectionController(context: AccountContext, update
guard let data = try? Data(contentsOf: url) else { guard let data = try? Data(contentsOf: url) else {
return return
} }
if data.count > 200 * 1024 { if data.count > settings.maxSize {
//TODO:localize //TODO:localize
presentUndo?(.info(title: "Audio is too large", text: "The file is over 200 KB.")) presentUndo(.info(title: "Audio is too large", text: "The file is over \(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData)))."))
return return
} }
@ -489,14 +504,14 @@ public func notificationSoundSelectionController(context: AccountContext, update
let duration = track.timeRange.duration.seconds let duration = track.timeRange.duration.seconds
Queue.mainQueue().async { Queue.mainQueue().async {
if duration > 5.0 { if duration > Double(settings.maxDuration) {
//TODO:localize //TODO:localize
presentUndo?(.info(title: "\(url.lastPathComponent) is too long", text: "The duration is longer than 5 seconds.")) presentUndo(.info(title: "\(url.lastPathComponent) is too long", text: "The duration is longer than \(stringForDuration(Int32(settings.maxDuration)))."))
} else { } else {
soundActionDisposable.set((context.engine.peers.uploadNotificationSound(title: url.lastPathComponent, data: data) disposable.set((context.engine.peers.uploadNotificationSound(title: url.lastPathComponent, data: data)
|> deliverOnMainQueue).start(next: { _ in |> deliverOnMainQueue).start(next: { _ in
//TODO:localize //TODO:localize
presentUndo?(.notificationSoundAdded(title: "Sound Added", text: "The sound **\(url.deletingPathExtension().lastPathComponent)** was added to your Telegram tones.**", action: nil)) presentUndo(.notificationSoundAdded(title: "Sound Added", text: "The sound **\(url.deletingPathExtension().lastPathComponent)** was added to your Telegram tones.**", action: nil))
}, error: { _ in }, error: { _ in
})) }))
} }
@ -505,15 +520,4 @@ public func notificationSoundSelectionController(context: AccountContext, update
} }
}) })
}), in: .window(.root)) }), in: .window(.root))
}
presentUndo = { [weak controller] content in
guard let controller = controller else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
return controller
} }

View File

@ -17,30 +17,30 @@ private enum NotificationPeerExceptionSection: Int32 {
case remove case remove
case switcher case switcher
case displayPreviews case displayPreviews
case soundCloud
case soundModern case soundModern
case soundClassic case soundClassic
} }
private enum NotificationPeerExceptionSwitcher : Equatable { private enum NotificationPeerExceptionSwitcher: Hashable {
case alwaysOn case alwaysOn
case alwaysOff case alwaysOff
} }
private enum NotificationPeerExceptionEntryId : Hashable { private enum NotificationPeerExceptionEntryId: Hashable {
case remove case remove
case switcher(NotificationPeerExceptionSwitcher) case switcher(NotificationPeerExceptionSwitcher)
case sound(PeerMessageSound) case sound(PeerMessageSound.Id)
case switcherHeader case switcherHeader
case displayPreviews(NotificationPeerExceptionSwitcher) case displayPreviews(NotificationPeerExceptionSwitcher)
case displayPreviewsHeader case displayPreviewsHeader
case soundModernHeader case soundModernHeader
case soundClassicHeader case soundClassicHeader
case none case none
case uploadSound
case cloudHeader
case cloudInfo
case `default` case `default`
func hash(into hasher: inout Hasher) {
hasher.combine(0)
}
} }
private final class NotificationPeerExceptionArguments { private final class NotificationPeerExceptionArguments {
@ -52,8 +52,10 @@ private final class NotificationPeerExceptionArguments {
let removeFromExceptions: () -> Void let removeFromExceptions: () -> Void
let complete: () -> Void let complete: () -> Void
let cancel: () -> Void let cancel: () -> Void
let upload: () -> Void
let deleteSound: (PeerMessageSound) -> Void
init(account: Account, selectSound: @escaping(PeerMessageSound) -> Void, selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void, selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void, removeFromExceptions: @escaping () -> Void, complete: @escaping()->Void, cancel: @escaping() -> Void) { init(account: Account, selectSound: @escaping(PeerMessageSound) -> Void, selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void, selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void, removeFromExceptions: @escaping () -> Void, complete: @escaping()->Void, cancel: @escaping() -> Void, upload: @escaping () -> Void, deleteSound: @escaping (PeerMessageSound) -> Void) {
self.account = account self.account = account
self.selectSound = selectSound self.selectSound = selectSound
self.selectMode = selectMode self.selectMode = selectMode
@ -61,6 +63,8 @@ private final class NotificationPeerExceptionArguments {
self.removeFromExceptions = removeFromExceptions self.removeFromExceptions = removeFromExceptions
self.complete = complete self.complete = complete
self.cancel = cancel self.cancel = cancel
self.upload = upload
self.deleteSound = deleteSound
} }
} }
@ -77,8 +81,10 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
case soundClassicHeader(index:Int32, theme: PresentationTheme, title: String) case soundClassicHeader(index:Int32, theme: PresentationTheme, title: String)
case none(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, selected: Bool) case none(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, selected: Bool)
case `default`(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, selected: Bool) case `default`(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, selected: Bool)
case sound(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, sound: PeerMessageSound, selected: Bool) case sound(index:Int32, section: NotificationPeerExceptionSection, theme: PresentationTheme, text: String, sound: PeerMessageSound, selected: Bool, canBeDeleted: Bool)
case cloudHeader(index: Int32, text: String)
case uploadSound(index: Int32, text: String)
case cloudInfo(index: Int32, text: String)
var index: Int32 { var index: Int32 {
switch self { switch self {
@ -100,7 +106,13 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return index return index
case let .default(index, _, _, _, _): case let .default(index, _, _, _, _):
return index return index
case let .sound(index, _, _, _, _, _): case let .sound(index, _, _, _, _, _, _):
return index
case let .cloudHeader(index, _):
return index
case let .cloudInfo(index, _):
return index
case let .uploadSound(index, _):
return index return index
} }
} }
@ -113,6 +125,8 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return NotificationPeerExceptionSection.switcher.rawValue return NotificationPeerExceptionSection.switcher.rawValue
case .displayPreviews, .displayPreviewsHeader: case .displayPreviews, .displayPreviewsHeader:
return NotificationPeerExceptionSection.displayPreviews.rawValue return NotificationPeerExceptionSection.displayPreviews.rawValue
case .cloudInfo, .cloudHeader, .uploadSound:
return NotificationPeerExceptionSection.soundCloud.rawValue
case .soundModernHeader: case .soundModernHeader:
return NotificationPeerExceptionSection.soundModern.rawValue return NotificationPeerExceptionSection.soundModern.rawValue
case .soundClassicHeader: case .soundClassicHeader:
@ -121,7 +135,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return section.rawValue return section.rawValue
case let .default(_, section, _, _, _): case let .default(_, section, _, _, _):
return section.rawValue return section.rawValue
case let .sound(_, section, _, _, _, _): case let .sound(_, section, _, _, _, _, _):
return section.rawValue return section.rawValue
} }
} }
@ -146,8 +160,14 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return .none return .none
case .default: case .default:
return .default return .default
case let .sound(_, _, _, _, sound, _): case let .sound(_, _, _, _, sound, _, _):
return .sound(sound) return .sound(sound.id)
case .uploadSound:
return .uploadSound
case .cloudHeader:
return .cloudHeader
case .cloudInfo:
return .cloudInfo
} }
} }
@ -186,6 +206,15 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectDisplayPreviews(value) arguments.selectDisplayPreviews(value)
}) })
case let .cloudHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .cloudInfo(_, text):
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: {
arguments.upload()
})
case let .displayPreviewsHeader(_, _, text): case let .displayPreviewsHeader(_, _, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .soundModernHeader(_, _, text): case let .soundModernHeader(_, _, text):
@ -200,17 +229,21 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(.default) arguments.selectSound(.default)
}) })
case let .sound(_, _, _, text, sound, selected): case let .sound(_, _, _, text, sound, selected, canBeDeleted):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(sound) arguments.selectSound(sound)
}) }, deleteAction: canBeDeleted ? {
arguments.deleteSound(sound)
} : nil)
} }
} }
} }
private func notificationPeerExceptionEntries(presentationData: PresentationData, notificationSoundList: NotificationSoundList?, state: NotificationExceptionPeerState) -> [NotificationPeerExceptionEntry] { private func notificationPeerExceptionEntries(presentationData: PresentationData, notificationSoundList: NotificationSoundList?, state: NotificationExceptionPeerState) -> [NotificationPeerExceptionEntry] {
var entries:[NotificationPeerExceptionEntry] = [] let selectedSound = resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList)
var entries: [NotificationPeerExceptionEntry] = []
var index: Int32 = 0 var index: Int32 = 0
@ -236,18 +269,43 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
entries.append(.displayPreviews(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOff, selected: state.displayPreviews == .alwaysOff)) entries.append(.displayPreviews(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOff, selected: state.displayPreviews == .alwaysOff))
index += 1 index += 1
entries.append(.cloudHeader(index: index, text: presentationData.strings.Notifications_TelegramTones))
index += 1
index = 1000
//entries.append(.none(section: .cloud, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: selectedSound == .none))
if let notificationSoundList = notificationSoundList {
for listSound in notificationSoundList.sounds {
let sound: PeerMessageSound = .cloud(fileId: listSound.file.fileId.id)
if state.removedSounds.contains(where: { $0.id == sound.id }) {
continue
}
entries.append(.sound(index: index, section: .soundCloud, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: selectedSound.id == sound.id, canBeDeleted: true))
index += 1
}
}
index = 2000
entries.append(.uploadSound(index: index, text: presentationData.strings.Notifications_UploadSound))
index += 1
entries.append(.cloudInfo(index: index, text: presentationData.strings.Notifications_MessageSoundInfo))
index += 1
entries.append(.soundModernHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notifications_AlertTones)) entries.append(.soundModernHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notifications_AlertTones))
index = 3000
entries.append(.default(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .default, default: state.defaultSound), selected: selectedSound == .default))
index += 1 index += 1
entries.append(.default(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .default, default: state.defaultSound), selected: state.selectedSound == .default)) entries.append(.none(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: selectedSound == .none))
index += 1
entries.append(.none(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: state.selectedSound == .none))
index += 1 index += 1
for i in 0 ..< 12 { for i in 0 ..< 12 {
let sound: PeerMessageSound = .bundledModern(id: Int32(i)) let sound: PeerMessageSound = .bundledModern(id: Int32(i))
entries.append(.sound(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound == state.selectedSound)) entries.append(.sound(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == selectedSound.id, canBeDeleted: false))
index += 1 index += 1
} }
@ -256,7 +314,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
for i in 0 ..< 8 { for i in 0 ..< 8 {
let sound: PeerMessageSound = .bundledClassic(id: Int32(i)) let sound: PeerMessageSound = .bundledClassic(id: Int32(i))
entries.append(.sound(index: index, section: .soundClassic, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound == state.selectedSound)) entries.append(.sound(index: index, section: .soundClassic, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == selectedSound.id, canBeDeleted: false))
index += 1 index += 1
} }
} }
@ -265,11 +323,12 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
} }
private struct NotificationExceptionPeerState : Equatable { private struct NotificationExceptionPeerState : Equatable {
let canRemove: Bool var canRemove: Bool
let selectedSound: PeerMessageSound var selectedSound: PeerMessageSound
let mode: NotificationPeerExceptionSwitcher var mode: NotificationPeerExceptionSwitcher
let defaultSound: PeerMessageSound var defaultSound: PeerMessageSound
let displayPreviews: NotificationPeerExceptionSwitcher var displayPreviews: NotificationPeerExceptionSwitcher
var removedSounds: [PeerMessageSound]
init(canRemove: Bool, notifications: TelegramPeerNotificationSettings? = nil) { init(canRemove: Bool, notifications: TelegramPeerNotificationSettings? = nil) {
self.canRemove = canRemove self.canRemove = canRemove
@ -290,31 +349,19 @@ private struct NotificationExceptionPeerState : Equatable {
} }
self.defaultSound = .default self.defaultSound = .default
self.removedSounds = []
} }
init(canRemove: Bool, selectedSound: PeerMessageSound, mode: NotificationPeerExceptionSwitcher, defaultSound: PeerMessageSound, displayPreviews: NotificationPeerExceptionSwitcher) { init(canRemove: Bool, selectedSound: PeerMessageSound, mode: NotificationPeerExceptionSwitcher, defaultSound: PeerMessageSound, displayPreviews: NotificationPeerExceptionSwitcher, removedSounds: [PeerMessageSound]) {
self.canRemove = canRemove self.canRemove = canRemove
self.selectedSound = selectedSound self.selectedSound = selectedSound
self.mode = mode self.mode = mode
self.defaultSound = defaultSound self.defaultSound = defaultSound
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
} self.removedSounds = removedSounds
func withUpdatedDefaultSound(_ defaultSound: PeerMessageSound) -> NotificationExceptionPeerState {
return NotificationExceptionPeerState(canRemove: self.canRemove, selectedSound: self.selectedSound, mode: self.mode, defaultSound: defaultSound, displayPreviews: self.displayPreviews)
}
func withUpdatedSound(_ selectedSound: PeerMessageSound) -> NotificationExceptionPeerState {
return NotificationExceptionPeerState(canRemove: self.canRemove, selectedSound: selectedSound, mode: self.mode, defaultSound: self.defaultSound, displayPreviews: self.displayPreviews)
}
func withUpdatedMode(_ mode: NotificationPeerExceptionSwitcher) -> NotificationExceptionPeerState {
return NotificationExceptionPeerState(canRemove: self.canRemove, selectedSound: self.selectedSound, mode: mode, defaultSound: self.defaultSound, displayPreviews: self.displayPreviews)
}
func withUpdatedDisplayPreviews(_ displayPreviews: NotificationPeerExceptionSwitcher) -> NotificationExceptionPeerState {
return NotificationExceptionPeerState(canRemove: self.canRemove, selectedSound: self.selectedSound, mode: self.mode, defaultSound: self.defaultSound, displayPreviews: displayPreviews)
} }
} }
public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: Peer, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: Peer, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController {
let initialState = NotificationExceptionPeerState(canRemove: false) let initialState = NotificationExceptionPeerState(canRemove: false)
let statePromise = Promise(initialState) let statePromise = Promise(initialState)
@ -327,6 +374,9 @@ public func notificationPeerExceptionController(context: AccountContext, updated
var removeFromExceptionsImpl: (() -> Void)? var removeFromExceptionsImpl: (() -> Void)?
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?
let playSoundDisposable = MetaDisposable() let playSoundDisposable = MetaDisposable()
var presentFilePicker: (() -> Void)?
let soundActionDisposable = MetaDisposable()
let arguments = NotificationPeerExceptionArguments(account: context.account, selectSound: { sound in let arguments = NotificationPeerExceptionArguments(account: context.account, selectSound: { sound in
updateState { state in updateState { state in
@ -336,15 +386,21 @@ public func notificationPeerExceptionController(context: AccountContext, updated
playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start()) playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start())
}) })
return state.withUpdatedSound(sound) var state = state
state.selectedSound = sound
return state
} }
}, selectMode: { mode in }, selectMode: { mode in
updateState { state in updateState { state in
return state.withUpdatedMode(mode) var state = state
state.mode = mode
return state
} }
}, selectDisplayPreviews: { value in }, selectDisplayPreviews: { value in
updateState { state in updateState { state in
return state.withUpdatedDisplayPreviews(value) var state = state
state.displayPreviews = value
return state
} }
}, removeFromExceptions: { }, removeFromExceptions: {
removeFromExceptionsImpl?() removeFromExceptionsImpl?()
@ -352,6 +408,27 @@ public func notificationPeerExceptionController(context: AccountContext, updated
completeImpl?() completeImpl?()
}, cancel: { }, cancel: {
cancelImpl?() cancelImpl?()
}, upload: {
presentFilePicker?()
}, deleteSound: { sound in
updateState { state in
var state = state
state.removedSounds.append(sound)
if state.selectedSound.id == sound.id {
state.selectedSound = .bundledModern(id: 0)
}
return state
}
switch sound {
case let .cloud(id):
soundActionDisposable.set((context.engine.peers.deleteNotificationSound(fileId: id)
|> deliverOnMainQueue).start(completed: {
}))
default:
break
}
}) })
statePromise.set(context.account.postbox.transaction { transaction -> NotificationExceptionPeerState in statePromise.set(context.account.postbox.transaction { transaction -> NotificationExceptionPeerState in
@ -359,16 +436,17 @@ public func notificationPeerExceptionController(context: AccountContext, updated
let globalSettings: GlobalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings let globalSettings: GlobalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings
switch mode { switch mode {
case .channels: case .channels:
state = state.withUpdatedDefaultSound(globalSettings.effective.channels.sound) state.defaultSound = globalSettings.effective.channels.sound
case .groups: case .groups:
state = state.withUpdatedDefaultSound(globalSettings.effective.groupChats.sound) state.defaultSound = globalSettings.effective.groupChats.sound
case .users: case .users:
state = state.withUpdatedDefaultSound(globalSettings.effective.privateChats.sound) state.defaultSound = globalSettings.effective.privateChats.sound
} }
_ = stateValue.swap(state) let _ = stateValue.swap(state)
return state return state
}) })
let previousSoundIds = Atomic<Set<Int64>>(value: Set())
let signal = combineLatest(queue: .mainQueue(), (updatedPresentationData?.signal ?? context.sharedContext.presentationData), context.engine.peers.notificationSoundList(), statePromise.get() |> distinctUntilChanged) let signal = combineLatest(queue: .mainQueue(), (updatedPresentationData?.signal ?? context.sharedContext.presentationData), context.engine.peers.notificationSoundList(), statePromise.get() |> distinctUntilChanged)
|> map { presentationData, notificationSoundList, state -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, notificationSoundList, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
@ -380,14 +458,30 @@ public func notificationPeerExceptionController(context: AccountContext, updated
arguments.complete() arguments.complete()
}) })
var updatedSoundIds = Set<Int64>()
if let notificationSoundList = notificationSoundList {
for sound in notificationSoundList.sounds {
if state.removedSounds.contains(.cloud(fileId: sound.file.fileId.id)) {
continue
}
updatedSoundIds.insert(sound.file.fileId.id)
}
}
var animated = false
if previousSoundIds.swap(updatedSoundIds) != updatedSoundIds {
animated = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: notificationPeerExceptionEntries(presentationData: presentationData, notificationSoundList: notificationSoundList, state: state), style: .blocks, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: notificationPeerExceptionEntries(presentationData: presentationData, notificationSoundList: notificationSoundList, state: state), style: .blocks, animateChanges: animated)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
let controller = ItemListController(context: context, state: signal |> afterDisposed { let controller = ItemListController(context: context, state: signal |> afterDisposed {
playSoundDisposable.dispose() playSoundDisposable.dispose()
soundActionDisposable.dispose()
}) })
controller.enableInteractiveDismiss = true controller.enableInteractiveDismiss = true
@ -395,12 +489,17 @@ public func notificationPeerExceptionController(context: AccountContext, updated
completeImpl = { [weak controller] in completeImpl = { [weak controller] in
controller?.dismiss() controller?.dismiss()
modifiedPeer() modifiedPeer()
let _ = (context.engine.peers.notificationSoundList()
|> take(1)
|> deliverOnMainQueue).start(next: { notificationSoundList in
updateState { state in updateState { state in
updatePeerSound(peer.id, state.selectedSound) updatePeerSound(peer.id, resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList))
updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max) updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max)
updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide) updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide)
return state return state
} }
})
} }
removeFromExceptionsImpl = { [weak controller] in removeFromExceptionsImpl = { [weak controller] in
@ -412,5 +511,12 @@ public func notificationPeerExceptionController(context: AccountContext, updated
controller?.dismiss() controller?.dismiss()
} }
presentFilePicker = { [weak controller] in
guard let controller = controller else {
return
}
presentCustomNotificationSoundFilePicker(context: context, controller: controller, disposable: soundActionDisposable)
}
return controller return controller
} }

View File

@ -871,10 +871,22 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} }
for media in message.media { for media in message.media {
if let file = media as? TelegramMediaFile, let size = file.size, size < 200 * 1024, (["audio/mpeg", "audio/mp3", "audio/mpeg3"] as [String]).contains(file.mimeType.lowercased()) { 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 actions.append(.action(ContextMenuActionItem(text: "Save for Notifications", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, 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)) let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
//TODO:localize //TODO:localize
@ -882,8 +894,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil)) controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil))
})) }))
}) })
}
f(.default)
}))) })))
/* /*