mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Notification sound updates
This commit is contained in:
parent
ed012a56cb
commit
eb9ff7beda
@ -108,8 +108,10 @@ public func parseMarkdownIntoAttributedString(_ string: String, attributes: Mark
|
||||
}
|
||||
result.append(NSAttributedString(string: bold, attributes: boldAttributes))
|
||||
} else {
|
||||
result.append(NSAttributedString(string: nsString.substring(with: NSMakeRange(remainingRange.location, 1)), attributes: bodyAttributes))
|
||||
remainingRange = NSMakeRange(range.location + 1, remainingRange.length - 1)
|
||||
if remainingRange.length != 0 {
|
||||
result.append(NSAttributedString(string: nsString.substring(with: NSMakeRange(remainingRange.location, 1)), attributes: bodyAttributes))
|
||||
remainingRange = NSMakeRange(range.location + 1, remainingRange.length - 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if result.string.hasSuffix("\\") {
|
||||
|
@ -451,69 +451,73 @@ public func notificationSoundSelectionController(context: AccountContext, update
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
var presentUndo: ((UndoOverlayContent) -> Void)?
|
||||
|
||||
|
||||
presentFilePicker = { [weak controller] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.mp3"], completion: { urls in
|
||||
guard !urls.isEmpty, let url = urls.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
var error: NSError?
|
||||
coordinator.coordinate(readingItemAt: url, options: .forUploading, error: &error, byAccessor: { url in
|
||||
Queue.mainQueue().async {
|
||||
let asset = AVAsset(url: url)
|
||||
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
if data.count > 200 * 1024 {
|
||||
//TODO:localize
|
||||
presentUndo?(.info(title: "Audio is too large", text: "The file is over 200 KB."))
|
||||
return
|
||||
}
|
||||
|
||||
asset.loadValuesAsynchronously(forKeys: ["tracks", "duration"], completionHandler: {
|
||||
if asset.statusOfValue(forKey: "duration", error: nil) != .loaded {
|
||||
return
|
||||
}
|
||||
guard let track = asset.tracks(withMediaType: .audio).first else {
|
||||
return
|
||||
}
|
||||
let duration = track.timeRange.duration.seconds
|
||||
|
||||
Queue.mainQueue().async {
|
||||
if duration > 5.0 {
|
||||
//TODO:localize
|
||||
presentUndo?(.info(title: "\(url.lastPathComponent) is too long", text: "The duration is longer than 5 seconds."))
|
||||
} else {
|
||||
soundActionDisposable.set((context.engine.peers.uploadNotificationSound(title: url.lastPathComponent, data: data)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
//TODO:localize
|
||||
presentUndo?(.notificationSoundAdded(title: "Sound Added", text: "The sound **\(url.deletingPathExtension().lastPathComponent)** was added to your Telegram tones.**", action: nil))
|
||||
}, error: { _ in
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}), 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)
|
||||
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 }
|
||||
controller.present(legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.mp3"], completion: { urls in
|
||||
guard !urls.isEmpty, let url = urls.first else {
|
||||
return
|
||||
}
|
||||
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
var error: NSError?
|
||||
coordinator.coordinate(readingItemAt: url, options: .forUploading, error: &error, byAccessor: { url in
|
||||
Queue.mainQueue().async {
|
||||
let asset = AVAsset(url: url)
|
||||
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
if data.count > 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
|
||||
}
|
||||
|
||||
asset.loadValuesAsynchronously(forKeys: ["tracks", "duration"], completionHandler: {
|
||||
if asset.statusOfValue(forKey: "duration", error: nil) != .loaded {
|
||||
return
|
||||
}
|
||||
guard let track = asset.tracks(withMediaType: .audio).first else {
|
||||
return
|
||||
}
|
||||
let duration = track.timeRange.duration.seconds
|
||||
|
||||
Queue.mainQueue().async {
|
||||
if duration > Double(settings.maxDuration) {
|
||||
//TODO:localize
|
||||
presentUndo(.info(title: "\(url.lastPathComponent) is too long", text: "The duration is longer than \(stringForDuration(Int32(settings.maxDuration)))."))
|
||||
} else {
|
||||
disposable.set((context.engine.peers.uploadNotificationSound(title: url.lastPathComponent, data: data)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
//TODO:localize
|
||||
presentUndo(.notificationSoundAdded(title: "Sound Added", text: "The sound **\(url.deletingPathExtension().lastPathComponent)** was added to your Telegram tones.**", action: nil))
|
||||
}, error: { _ in
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}), in: .window(.root))
|
||||
}
|
||||
|
@ -17,30 +17,30 @@ private enum NotificationPeerExceptionSection: Int32 {
|
||||
case remove
|
||||
case switcher
|
||||
case displayPreviews
|
||||
case soundCloud
|
||||
case soundModern
|
||||
case soundClassic
|
||||
}
|
||||
|
||||
private enum NotificationPeerExceptionSwitcher : Equatable {
|
||||
private enum NotificationPeerExceptionSwitcher: Hashable {
|
||||
case alwaysOn
|
||||
case alwaysOff
|
||||
}
|
||||
|
||||
private enum NotificationPeerExceptionEntryId : Hashable {
|
||||
private enum NotificationPeerExceptionEntryId: Hashable {
|
||||
case remove
|
||||
case switcher(NotificationPeerExceptionSwitcher)
|
||||
case sound(PeerMessageSound)
|
||||
case sound(PeerMessageSound.Id)
|
||||
case switcherHeader
|
||||
case displayPreviews(NotificationPeerExceptionSwitcher)
|
||||
case displayPreviewsHeader
|
||||
case soundModernHeader
|
||||
case soundClassicHeader
|
||||
case none
|
||||
case uploadSound
|
||||
case cloudHeader
|
||||
case cloudInfo
|
||||
case `default`
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(0)
|
||||
}
|
||||
}
|
||||
|
||||
private final class NotificationPeerExceptionArguments {
|
||||
@ -52,8 +52,10 @@ private final class NotificationPeerExceptionArguments {
|
||||
let removeFromExceptions: () -> Void
|
||||
let complete: () -> 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.selectSound = selectSound
|
||||
self.selectMode = selectMode
|
||||
@ -61,6 +63,8 @@ private final class NotificationPeerExceptionArguments {
|
||||
self.removeFromExceptions = removeFromExceptions
|
||||
self.complete = complete
|
||||
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 none(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 {
|
||||
switch self {
|
||||
@ -100,7 +106,13 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
|
||||
return index
|
||||
case let .default(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
|
||||
}
|
||||
}
|
||||
@ -113,6 +125,8 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
|
||||
return NotificationPeerExceptionSection.switcher.rawValue
|
||||
case .displayPreviews, .displayPreviewsHeader:
|
||||
return NotificationPeerExceptionSection.displayPreviews.rawValue
|
||||
case .cloudInfo, .cloudHeader, .uploadSound:
|
||||
return NotificationPeerExceptionSection.soundCloud.rawValue
|
||||
case .soundModernHeader:
|
||||
return NotificationPeerExceptionSection.soundModern.rawValue
|
||||
case .soundClassicHeader:
|
||||
@ -121,7 +135,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
|
||||
return section.rawValue
|
||||
case let .default(_, section, _, _, _):
|
||||
return section.rawValue
|
||||
case let .sound(_, section, _, _, _, _):
|
||||
case let .sound(_, section, _, _, _, _, _):
|
||||
return section.rawValue
|
||||
}
|
||||
}
|
||||
@ -146,8 +160,14 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
|
||||
return .none
|
||||
case .default:
|
||||
return .default
|
||||
case let .sound(_, _, _, _, sound, _):
|
||||
return .sound(sound)
|
||||
case let .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: {
|
||||
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):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
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: {
|
||||
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: {
|
||||
arguments.selectSound(sound)
|
||||
})
|
||||
}, deleteAction: canBeDeleted ? {
|
||||
arguments.deleteSound(sound)
|
||||
} : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
@ -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))
|
||||
index += 1
|
||||
|
||||
entries.append(.soundModernHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notifications_AlertTones))
|
||||
entries.append(.cloudHeader(index: index, text: presentationData.strings.Notifications_TelegramTones))
|
||||
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))
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
entries.append(.none(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: state.selectedSound == .none))
|
||||
entries.append(.none(index: index, section: .soundModern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: selectedSound == .none))
|
||||
index += 1
|
||||
|
||||
for i in 0 ..< 12 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -256,7 +314,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
||||
|
||||
for i in 0 ..< 8 {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -265,11 +323,12 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
||||
}
|
||||
|
||||
private struct NotificationExceptionPeerState : Equatable {
|
||||
let canRemove: Bool
|
||||
let selectedSound: PeerMessageSound
|
||||
let mode: NotificationPeerExceptionSwitcher
|
||||
let defaultSound: PeerMessageSound
|
||||
let displayPreviews: NotificationPeerExceptionSwitcher
|
||||
var canRemove: Bool
|
||||
var selectedSound: PeerMessageSound
|
||||
var mode: NotificationPeerExceptionSwitcher
|
||||
var defaultSound: PeerMessageSound
|
||||
var displayPreviews: NotificationPeerExceptionSwitcher
|
||||
var removedSounds: [PeerMessageSound]
|
||||
|
||||
init(canRemove: Bool, notifications: TelegramPeerNotificationSettings? = nil) {
|
||||
self.canRemove = canRemove
|
||||
@ -290,31 +349,19 @@ private struct NotificationExceptionPeerState : Equatable {
|
||||
}
|
||||
|
||||
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.selectedSound = selectedSound
|
||||
self.mode = mode
|
||||
self.defaultSound = defaultSound
|
||||
self.displayPreviews = displayPreviews
|
||||
}
|
||||
|
||||
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)
|
||||
self.removedSounds = removedSounds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 statePromise = Promise(initialState)
|
||||
@ -327,6 +374,9 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|
||||
var removeFromExceptionsImpl: (() -> Void)?
|
||||
var cancelImpl: (() -> Void)?
|
||||
let playSoundDisposable = MetaDisposable()
|
||||
var presentFilePicker: (() -> Void)?
|
||||
|
||||
let soundActionDisposable = MetaDisposable()
|
||||
|
||||
let arguments = NotificationPeerExceptionArguments(account: context.account, selectSound: { sound 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())
|
||||
})
|
||||
|
||||
return state.withUpdatedSound(sound)
|
||||
var state = state
|
||||
state.selectedSound = sound
|
||||
return state
|
||||
}
|
||||
}, selectMode: { mode in
|
||||
updateState { state in
|
||||
return state.withUpdatedMode(mode)
|
||||
var state = state
|
||||
state.mode = mode
|
||||
return state
|
||||
}
|
||||
}, selectDisplayPreviews: { value in
|
||||
updateState { state in
|
||||
return state.withUpdatedDisplayPreviews(value)
|
||||
var state = state
|
||||
state.displayPreviews = value
|
||||
return state
|
||||
}
|
||||
}, removeFromExceptions: {
|
||||
removeFromExceptionsImpl?()
|
||||
@ -352,23 +408,45 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|
||||
completeImpl?()
|
||||
}, cancel: {
|
||||
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
|
||||
var state = NotificationExceptionPeerState(canRemove: mode.peerIds.contains(peer.id), notifications: transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings)
|
||||
let globalSettings: GlobalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings
|
||||
switch mode {
|
||||
case .channels:
|
||||
state = state.withUpdatedDefaultSound(globalSettings.effective.channels.sound)
|
||||
case .groups:
|
||||
state = state.withUpdatedDefaultSound(globalSettings.effective.groupChats.sound)
|
||||
case .users:
|
||||
state = state.withUpdatedDefaultSound(globalSettings.effective.privateChats.sound)
|
||||
case .channels:
|
||||
state.defaultSound = globalSettings.effective.channels.sound
|
||||
case .groups:
|
||||
state.defaultSound = globalSettings.effective.groupChats.sound
|
||||
case .users:
|
||||
state.defaultSound = globalSettings.effective.privateChats.sound
|
||||
}
|
||||
_ = stateValue.swap(state)
|
||||
let _ = stateValue.swap(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)
|
||||
|> map { presentationData, notificationSoundList, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
@ -380,14 +458,30 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|
||||
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 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))
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal |> afterDisposed {
|
||||
playSoundDisposable.dispose()
|
||||
soundActionDisposable.dispose()
|
||||
})
|
||||
|
||||
controller.enableInteractiveDismiss = true
|
||||
@ -395,12 +489,17 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|
||||
completeImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
modifiedPeer()
|
||||
updateState { state in
|
||||
updatePeerSound(peer.id, state.selectedSound)
|
||||
updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max)
|
||||
updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide)
|
||||
return state
|
||||
}
|
||||
|
||||
let _ = (context.engine.peers.notificationSoundList()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { notificationSoundList in
|
||||
updateState { state in
|
||||
updatePeerSound(peer.id, resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList))
|
||||
updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max)
|
||||
updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide)
|
||||
return state
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removeFromExceptionsImpl = { [weak controller] in
|
||||
@ -411,6 +510,13 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|
||||
cancelImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
presentFilePicker = { [weak controller] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
presentCustomNotificationSoundFilePicker(context: context, controller: controller, disposable: soundActionDisposable)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -871,19 +871,30 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
|
||||
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
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
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))
|
||||
}))
|
||||
})
|
||||
|
||||
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))
|
||||
}))
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user