Notification sounds update

This commit is contained in:
Ali 2022-03-31 01:19:30 +04:00
parent c278a5af7d
commit f45de2d44f
9 changed files with 345 additions and 95 deletions

View File

@ -544,52 +544,55 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private func initializeContent() {
switch self.source {
case let .reference(source):
/*let transitionInfo = source.transitionInfo()
if let transitionInfo = transitionInfo {
let referenceView = transitionInfo.referenceView
self.contentContainerNode.contentNode = .reference(view: referenceView)
self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
self.customPosition = transitionInfo.customPosition
var projectedFrame = convertFrame(referenceView.bounds, from: referenceView, to: self.view)
projectedFrame.origin.x += transitionInfo.insets.left
projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right
projectedFrame.origin.y += transitionInfo.insets.top
projectedFrame.size.width -= transitionInfo.insets.top + transitionInfo.insets.bottom
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
}*/
let presentationNode = ContextControllerExtractedPresentationNode(
getController: { [weak self] in
return self?.getController()
},
requestUpdate: { [weak self] transition in
guard let strongSelf = self else {
return
}
if let validLayout = strongSelf.validLayout {
strongSelf.updateLayout(
layout: validLayout,
transition: transition,
previousActionsContainerNode: nil
)
}
},
requestDismiss: { [weak self] result in
guard let strongSelf = self else {
return
}
strongSelf.dismissedForCancel?()
strongSelf.beginDismiss(result)
},
requestAnimateOut: { [weak self] result, completion in
guard let strongSelf = self else {
return
}
strongSelf.animateOut(result: result, completion: completion)
},
source: .reference(source)
)
self.presentationNode = presentationNode
self.addSubnode(presentationNode)
if let controller = self.getController() as? ContextController, controller.workaroundUseLegacyImplementation {
let transitionInfo = source.transitionInfo()
if let transitionInfo = transitionInfo {
let referenceView = transitionInfo.referenceView
self.contentContainerNode.contentNode = .reference(view: referenceView)
self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
self.customPosition = transitionInfo.customPosition
var projectedFrame = convertFrame(referenceView.bounds, from: referenceView, to: self.view)
projectedFrame.origin.x += transitionInfo.insets.left
projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right
projectedFrame.origin.y += transitionInfo.insets.top
projectedFrame.size.width -= transitionInfo.insets.top + transitionInfo.insets.bottom
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
}
} else {
let presentationNode = ContextControllerExtractedPresentationNode(
getController: { [weak self] in
return self?.getController()
},
requestUpdate: { [weak self] transition in
guard let strongSelf = self else {
return
}
if let validLayout = strongSelf.validLayout {
strongSelf.updateLayout(
layout: validLayout,
transition: transition,
previousActionsContainerNode: nil
)
}
},
requestDismiss: { [weak self] result in
guard let strongSelf = self else {
return
}
strongSelf.dismissedForCancel?()
strongSelf.beginDismiss(result)
},
requestAnimateOut: { [weak self] result, completion in
guard let strongSelf = self else {
return
}
strongSelf.animateOut(result: result, completion: completion)
},
source: .reference(source)
)
self.presentationNode = presentationNode
self.addSubnode(presentationNode)
}
case let .extracted(source):
let presentationNode = ContextControllerExtractedPresentationNode(
getController: { [weak self] in
@ -2251,6 +2254,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
public var useComplexItemsTransitionAnimation = false
public var immediateItemsTransitionAnimation = false
public var workaroundUseLegacyImplementation = false
public enum HandledTouchEvent {
case ignore

View File

@ -32,8 +32,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
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) {
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) {
self.presentationData = presentationData
self.icon = icon
self.iconSize = iconSize
@ -45,6 +46,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
self.zeroSeparatorInsets = zeroSeparatorInsets
self.sectionId = sectionId
self.action = action
self.deleteAction = deleteAction
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -88,7 +90,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
}
}
public class ItemListCheckboxItemNode: ListViewItemNode {
public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
@ -97,6 +99,7 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
private let activateArea: AccessibilityAreaNode
private let contentContainerNode: ASDisplayNode
private let imageNode: ASImageNode
private let iconNode: ASImageNode
private let titleNode: TextNode
@ -115,6 +118,8 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
self.maskNode = ASImageNode()
self.contentContainerNode = ASDisplayNode()
self.imageNode = ASImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.displayWithoutProcessing = true
@ -135,11 +140,12 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.imageNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.imageNode)
self.contentContainerNode.addSubnode(self.iconNode)
self.contentContainerNode.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
@ -204,6 +210,8 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.contentContainerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
if item.checked {
strongSelf.activateArea.accessibilityValue = "Selected"
@ -297,6 +305,14 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.deleteAction != nil {
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
} else {
strongSelf.setRevealOptions((left: [], right: []))
}
}
})
}
@ -347,4 +363,61 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(x: offset, y: self.contentContainerNode.frame.minY), size: self.contentContainerNode.bounds.size))
/*if let (item, params, _, _, _) = self.layoutParams {
let revealOffset = offset
let editingOffset: CGFloat
if let editableControlNode = self.editableControlNode {
editingOffset = editableControlNode.bounds.size.width
var editableControlFrame = editableControlNode.frame
editableControlFrame.origin.x = params.leftInset + offset
transition.updateFrame(node: editableControlNode, frame: editableControlFrame)
} else {
editingOffset = 0.0
}
let leftInset: CGFloat = 86.0 + params.leftInset + editingOffset
let rightInset: CGFloat = 13.0 + params.rightInset
var infoIconRightInset: CGFloat = rightInset - 1.0
var dateRightInset: CGFloat = 46.0 + params.rightInset
if item.editing {
dateRightInset += 5.0
infoIconRightInset -= 36.0
}
var avatarFrame = self.avatarNode.frame
avatarFrame.origin.x = revealOffset + leftInset - 52.0
transition.updateFrameAdditive(node: self.avatarNode, frame: avatarFrame)
transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size))
transition.updateFrameAdditive(node: self.dateNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + self.bounds.size.width - dateRightInset - self.dateNode.bounds.size.width, y: self.dateNode.frame.minY), size: self.dateNode.bounds.size))
transition.updateFrameAdditive(node: self.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 81.0, y: self.typeIconNode.frame.minY), size: self.typeIconNode.bounds.size))
transition.updateFrameAdditive(node: self.infoButtonNode, frame: CGRect(origin: CGPoint(x: revealOffset + self.bounds.size.width - infoIconRightInset - self.infoButtonNode.bounds.width, y: self.infoButtonNode.frame.minY), size: self.infoButtonNode.bounds.size))
}*/
}
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
if let item = self.item {
item.deleteAction?()
}
/*if let item = self.layoutParams?.0 {
item.interaction.delete(item.messages.map { $0.id })
}*/
}
}

View File

@ -21,6 +21,7 @@ private struct NotificationSoundSelectionArguments {
let complete: () -> Void
let cancel: () -> Void
let upload: () -> Void
let deleteSound: (PeerMessageSound) -> Void
}
private enum NotificationSoundSelectionSection: Int32 {
@ -31,9 +32,15 @@ private enum NotificationSoundSelectionSection: Int32 {
private struct NotificationSoundSelectionState: Equatable {
var selectedSound: PeerMessageSound
var removedSounds: [PeerMessageSound]
}
private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
enum StableId: Hashable {
case index(Int32)
case sound(PeerMessageSound.Id)
}
case cloudHeader(String)
case uploadSound(String)
case cloudInfo(String)
@ -42,7 +49,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
case classicHeader(PresentationTheme, String)
case none(section: NotificationSoundSelectionSection, theme: PresentationTheme, text: String, selected: Bool)
case `default`(section: NotificationSoundSelectionSection, theme: PresentationTheme, text: String, selected: Bool)
case sound(section: NotificationSoundSelectionSection, index: Int32, theme: PresentationTheme, text: String, sound: PeerMessageSound, selected: Bool)
case sound(section: NotificationSoundSelectionSection, index: Int32, theme: PresentationTheme, text: String, sound: PeerMessageSound, selected: Bool, canBeDeleted: Bool)
var section: ItemListSectionId {
switch self {
@ -56,7 +63,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
return section.rawValue
case let .default(section, _, _, _):
return section.rawValue
case let .sound(section, _, _, _, _, _):
case let .sound(section, _, _, _, _, _, _):
return section.rawValue
}
}
@ -91,7 +98,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
case .classic:
return 2002
}
case let .sound(section, index, _, _, _, _):
case let .sound(section, index, _, _, _, _, _):
switch section {
case .cloud:
return 3 + index
@ -103,8 +110,13 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
}
}
var stableId: Int32 {
return self.sortId
var stableId: StableId {
switch self {
case let .sound(_ , _, _, _, sound, _, _):
return .sound(sound.id)
default:
return .index(self.sortId)
}
}
static func ==(lhs: NotificationSoundSelectionEntry, rhs: NotificationSoundSelectionEntry) -> Bool {
@ -151,8 +163,8 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
} else {
return false
}
case let .sound(lhsSection, lhsIndex, lhsTheme, lhsText, lhsSound, lhsSelected):
if case let .sound(rhsSection, rhsIndex, rhsTheme, rhsText, rhsSound, rhsSelected) = rhs, lhsSection == rhsSection, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsSound == rhsSound, lhsSelected == rhsSelected {
case let .sound(lhsSection, lhsIndex, lhsTheme, lhsText, lhsSound, lhsSelected, lhsCanBeDeleted):
if case let .sound(rhsSection, rhsIndex, rhsTheme, rhsText, rhsSound, rhsSelected, rhsCanBeDeleted) = rhs, lhsSection == rhsSection, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsText == rhsText, lhsSound == rhsSound, lhsSelected == rhsSelected, lhsCanBeDeleted == rhsCanBeDeleted {
return true
} else {
return false
@ -161,7 +173,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
}
static func <(lhs: NotificationSoundSelectionEntry, rhs: NotificationSoundSelectionEntry) -> Bool {
return lhs.stableId < rhs.stableId
return lhs.sortId < rhs.sortId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
@ -188,23 +200,30 @@ private enum NotificationSoundSelectionEntry: 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 notificationsAndSoundsEntries(presentationData: PresentationData, defaultSound: PeerMessageSound?, state: NotificationSoundSelectionState, notificationSoundList: NotificationSoundList?) -> [NotificationSoundSelectionEntry] {
let selectedSound = resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList)
var entries: [NotificationSoundSelectionEntry] = []
entries.append(.cloudHeader(presentationData.strings.Notifications_TelegramTones))
//entries.append(.none(section: .cloud, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: state.selectedSound == .none))
//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)
entries.append(.sound(section: .cloud, index: Int32(entries.count), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: state.selectedSound.id == sound.id))
if state.removedSounds.contains(where: { $0.id == sound.id }) {
continue
}
entries.append(.sound(section: .cloud, index: Int32(entries.count), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: selectedSound.id == sound.id, canBeDeleted: true))
}
}
entries.append(.uploadSound(presentationData.strings.Notifications_UploadSound))
@ -212,18 +231,18 @@ private func notificationsAndSoundsEntries(presentationData: PresentationData, d
entries.append(.modernHeader(presentationData.theme, presentationData.strings.Notifications_AlertTones))
if let defaultSound = defaultSound {
entries.append(.default(section: .modern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .default, default: defaultSound), selected: state.selectedSound.id == .default))
entries.append(.default(section: .modern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .default, default: defaultSound), selected: selectedSound.id == .default))
}
entries.append(.none(section: .modern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: state.selectedSound.id == .none))
entries.append(.none(section: .modern, theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: .none), selected: selectedSound.id == .none))
for i in 0 ..< 12 {
let sound: PeerMessageSound = .bundledModern(id: Int32(i))
entries.append(.sound(section: .modern, index: Int32(i), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == state.selectedSound.id))
entries.append(.sound(section: .modern, index: Int32(i), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == selectedSound.id, canBeDeleted: false))
}
entries.append(.classicHeader(presentationData.theme, presentationData.strings.Notifications_ClassicTones))
for i in 0 ..< 8 {
let sound: PeerMessageSound = .bundledClassic(id: Int32(i))
entries.append(.sound(section: .classic, index: Int32(i), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == state.selectedSound.id))
entries.append(.sound(section: .classic, index: Int32(i), theme: presentationData.theme, text: localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: sound), sound: sound, selected: sound.id == selectedSound.id, canBeDeleted: false))
}
return entries
@ -340,8 +359,8 @@ public func playSound(context: AccountContext, notificationSoundList: Notificati
}
public func notificationSoundSelectionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, isModal: Bool, currentSound: PeerMessageSound, defaultSound: PeerMessageSound?, completion: @escaping (PeerMessageSound) -> Void) -> ViewController {
let statePromise = ValuePromise(NotificationSoundSelectionState(selectedSound: currentSound), ignoreRepeated: true)
let stateValue = Atomic(value: NotificationSoundSelectionState(selectedSound: currentSound))
let statePromise = ValuePromise(NotificationSoundSelectionState(selectedSound: currentSound, removedSounds: []), ignoreRepeated: true)
let stateValue = Atomic(value: NotificationSoundSelectionState(selectedSound: currentSound, removedSounds: []))
let updateState: ((NotificationSoundSelectionState) -> NotificationSoundSelectionState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
@ -355,7 +374,11 @@ public func notificationSoundSelectionController(context: AccountContext, update
let arguments = NotificationSoundSelectionArguments(account: context.account, selectSound: { sound in
updateState { state in
return NotificationSoundSelectionState(selectedSound: sound)
var state = state
state.selectedSound = sound
return state
}
let _ = (context.engine.peers.notificationSoundList()
@ -369,6 +392,25 @@ public func notificationSoundSelectionController(context: AccountContext, update
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
}
})
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
@ -432,20 +474,32 @@ public func notificationSoundSelectionController(context: AccountContext, update
return
}
if data.count > 200 * 1024 {
presentUndo?(.info(title: "Audio is too large", text: "The file is larger than 200 KB."))
//TODO:localize
presentUndo?(.info(title: "Audio is too large", text: "The file is over 200 KB."))
return
}
asset.loadValuesAsynchronously(forKeys: ["duration"], completionHandler: {
let duration = asset.duration.seconds
if duration > 5.0 {
//TODO:localize
presentUndo?(.info(title: "Audio 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
}, error: { _ in
}))
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
}))
}
}
})
}

View File

@ -279,3 +279,59 @@ func _internal_uploadNotificationSound(account: Account, title: String, data: Da
}
}
}
public enum DeleteNotificationSoundError {
case generic
}
func _internal_deleteNotificationSound(account: Account, fileId: Int64) -> Signal<Never, DeleteNotificationSoundError> {
return account.postbox.transaction { transaction -> NotificationSoundList.NotificationSound? in
return _internal_cachedNotificationSoundList(transaction: transaction).flatMap { list -> NotificationSoundList.NotificationSound? in
return list.sounds.first(where: { $0.file.fileId.id == fileId })
}
}
|> castError(DeleteNotificationSoundError.self)
|> mapToSignal { sound -> Signal<Never, DeleteNotificationSoundError> in
guard let sound = sound else {
return .fail(.generic)
}
guard let resource = sound.file.resource as? CloudDocumentMediaResource else {
return .fail(.generic)
}
return account.network.request(Api.functions.account.saveRingtone(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), unsave: .boolTrue))
|> mapError { _ -> DeleteNotificationSoundError in
return .generic
}
|> mapToSignal { _ -> Signal<Never, DeleteNotificationSoundError> in
return account.postbox.transaction { transaction -> Void in
if let notificationSoundList = _internal_cachedNotificationSoundList(transaction: transaction) {
let updatedNotificationSoundList = NotificationSoundList(hash: notificationSoundList.hash, sounds: notificationSoundList.sounds.filter { item in
return item.file.fileId.id != fileId
})
_internal_setCachedNotificationSoundList(transaction: transaction, notificationSoundList: updatedNotificationSoundList)
}
}
|> castError(DeleteNotificationSoundError.self)
|> ignoreValues
}
}
}
public func resolvedNotificationSound(sound: PeerMessageSound, notificationSoundList: NotificationSoundList?) -> PeerMessageSound {
switch sound {
case let .cloud(fileId):
if let notificationSoundList = notificationSoundList {
for listSound in notificationSoundList.sounds {
if listSound.file.fileId.id == fileId {
return sound
}
}
return .bundledModern(id: 0)
} else {
return .default
}
default:
return sound
}
}

View File

@ -703,6 +703,10 @@ public extension TelegramEngine {
public func uploadNotificationSound(title: String, data: Data) -> Signal<NotificationSoundList.NotificationSound, UploadNotificationSoundError> {
return _internal_uploadNotificationSound(account: self.account, title: title, data: data)
}
public func deleteNotificationSound(fileId: Int64) -> Signal<Never, DeleteNotificationSoundError> {
return _internal_deleteNotificationSound(account: self.account, fileId: fileId)
}
}
}

View File

@ -51,7 +51,30 @@ private func soundName(strings: PresentationStrings, sound: PeerMessageSound, no
}
for sound in notificationSoundList.sounds {
if sound.file.fileId.id == fileId {
return sound.file.fileName ?? "Cloud Tone"
for attribute in sound.file.attributes {
switch attribute {
case let .Audio(_, _, title, performer, _):
if let title = title, !title.isEmpty, let performer = performer, !performer.isEmpty {
return "\(title) - \(performer)"
} else if let title = title, !title.isEmpty {
return title
} else if let performer = performer, !performer.isEmpty {
return performer
}
default:
break
}
}
if let fileName = sound.file.fileName, !fileName.isEmpty {
if let range = fileName.range(of: ".", options: .backwards) {
return String(fileName[fileName.startIndex ..< range.lowerBound])
} else {
return fileName
}
}
return "Cloud Tone"
}
}
return ""
@ -71,9 +94,16 @@ public func localizedPeerNotificationSoundString(strings: PresentationStrings, n
}
return strings.UserInfo_NotificationsDefaultSound(actualName).string
} else {
return strings.UserInfo_NotificationsDefault
let name = soundName(strings: strings, sound: .bundledModern(id: 0), notificationSoundList: notificationSoundList)
return name
//return strings.UserInfo_NotificationsDefault
}
default:
return soundName(strings: strings, sound: sound, notificationSoundList: notificationSoundList)
let name = soundName(strings: strings, sound: sound, notificationSoundList: notificationSoundList)
if name.isEmpty {
return localizedPeerNotificationSoundString(strings: strings, notificationSoundList: notificationSoundList, sound: .default, default: `default`)
} else {
return name
}
}
}

View File

@ -8222,6 +8222,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: node, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.workaroundUseLegacyImplementation = true
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(interactive: true, {

View File

@ -875,7 +875,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
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)).start(completed: {
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))

View File

@ -766,7 +766,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
@ -774,6 +776,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.originalRemainingSeconds = 5
if let action = action {
self.textNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
}
self.textNode.tapAttributeAction = { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
action()
@ -820,15 +829,33 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
super.init()
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded:
break
case .dice:
self.panelWrapperNode.clipsToBounds = true
case .info:
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded:
if self.textNode.tapAttributeAction != nil {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
case .dice:
self.panelWrapperNode.clipsToBounds = true
case .info:
if self.textNode.tapAttributeAction != nil {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
}
self.titleNode.isUserInteractionEnabled = false
self.textNode.isUserInteractionEnabled = self.textNode.tapAttributeAction != nil
self.iconNode?.isUserInteractionEnabled = false
self.animationNode?.isUserInteractionEnabled = false
self.iconCheckNode?.isUserInteractionEnabled = false
self.avatarNode?.isUserInteractionEnabled = false
self.slotMachineNode?.isUserInteractionEnabled = false
self.animatedStickerNode?.isUserInteractionEnabled = false
self.statusNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconCheckNode.flatMap(self.panelWrapperNode.addSubnode)
@ -837,9 +864,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.buttonNode)
self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode)
self.panelWrapperNode.addSubnode(self.buttonNode)
if displayUndo {
self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
self.panelWrapperNode.addSubnode(self.undoButtonNode)