SoundsViewController.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //
  2. // SoundsViewController.swift
  3. // Bark
  4. //
  5. // Created by huangfeng on 2020/9/14.
  6. // Copyright © 2020 Fin. All rights reserved.
  7. //
  8. import AVKit
  9. import Material
  10. import UIKit
  11. import NSObject_Rx
  12. import RxCocoa
  13. import RxDataSources
  14. import RxSwift
  15. class SoundsViewController: BaseViewController<SoundsViewModel> {
  16. let tableView: UITableView = {
  17. let tableView = UITableView(frame: CGRect.zero, style: .insetGrouped)
  18. tableView.backgroundColor = BKColor.background.primary
  19. tableView.register(SoundCell.self, forCellReuseIdentifier: "\(SoundCell.self)")
  20. tableView.register(AddSoundCell.self, forCellReuseIdentifier: "\(AddSoundCell.self)")
  21. return tableView
  22. }()
  23. // 上传铃声文件事件序列
  24. let importSoundActionRelay = PublishRelay<URL>()
  25. override func makeUI() {
  26. self.title = NSLocalizedString("notificationSound")
  27. self.view.addSubview(self.tableView)
  28. self.tableView.delegate = self
  29. self.tableView.snp.makeConstraints { make in
  30. make.edges.equalToSuperview()
  31. }
  32. }
  33. override func bindViewModel() {
  34. let output = viewModel.transform(
  35. input: SoundsViewModel.Input(
  36. soundSelected: self.tableView.rx.modelSelected(SoundItem.self).asDriver(),
  37. importSound: self.importSoundActionRelay.asDriver(onErrorDriveWith: .empty()),
  38. soundDeleted: self.tableView.rx.modelDeleted(SoundItem.self).asDriver()
  39. )
  40. )
  41. let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, SoundItem>> { _, tableView, _, item -> UITableViewCell in
  42. switch item {
  43. case .sound(let model):
  44. guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(SoundCell.self)") as? SoundCell else {
  45. return UITableViewCell()
  46. }
  47. cell.bindViewModel(model: model)
  48. return cell
  49. case .addSound:
  50. guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(AddSoundCell.self)") else {
  51. return UITableViewCell()
  52. }
  53. return cell
  54. }
  55. } titleForHeaderInSection: { dataSource, section in
  56. return dataSource[section].model
  57. } canEditRowAtIndexPath: { dataSource, indexPath in
  58. guard indexPath.section == 0 else {
  59. return false
  60. }
  61. guard case SoundItem.sound = dataSource[indexPath.section].items[indexPath.row] else {
  62. return false
  63. }
  64. return true
  65. }
  66. output.audios
  67. .bind(to: tableView.rx.items(dataSource: dataSource))
  68. .disposed(by: rx.disposeBag)
  69. output.copyNameAction.drive(onNext: { [unowned self] name in
  70. UIPasteboard.general.string = name.trimmingCharacters(in: .whitespacesAndNewlines)
  71. self.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
  72. }).disposed(by: rx.disposeBag)
  73. output.playAction.drive(onNext: { url in
  74. var soundID: SystemSoundID = 0
  75. AudioServicesCreateSystemSoundID(url, &soundID)
  76. AudioServicesPlaySystemSoundWithCompletion(soundID) {
  77. AudioServicesDisposeSystemSoundID(soundID)
  78. }
  79. }).disposed(by: rx.disposeBag)
  80. output.pickerFile.drive(onNext: { [unowned self] _ in
  81. self.pickerSoundFile()
  82. }).disposed(by: rx.disposeBag)
  83. }
  84. }
  85. extension SoundsViewController: UITableViewDelegate {
  86. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  87. return 40
  88. }
  89. func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  90. guard section == 0 else {
  91. return 0
  92. }
  93. return NSLocalizedString("uploadSoundNoticeFullText").count <= 30 ? 50 : 60
  94. }
  95. func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  96. let sectionTitle = tableView.dataSource?.tableView?(tableView, titleForHeaderInSection: section) ?? ""
  97. let view = UIView()
  98. let label = UILabel()
  99. label.text = NSLocalizedString(sectionTitle)
  100. label.fontSize = 14
  101. label.textColor = BKColor.grey.darken3
  102. view.addSubview(label)
  103. label.snp.makeConstraints { make in
  104. make.left.equalTo(12)
  105. make.centerY.equalToSuperview()
  106. }
  107. return view
  108. }
  109. func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
  110. guard section == 0 else {
  111. return nil
  112. }
  113. let view = UIView()
  114. let fullText = NSLocalizedString("uploadSoundNoticeFullText")
  115. let highlightText = NSLocalizedString("uploadSoundNoticeHighlightText")
  116. let attrStr = NSMutableAttributedString(
  117. string: fullText,
  118. attributes: [
  119. NSAttributedString.Key.foregroundColor: BKColor.grey.darken3,
  120. NSAttributedString.Key.font: RobotoFont.regular(with: 14)
  121. ]
  122. )
  123. attrStr.setAttributes([
  124. NSAttributedString.Key.foregroundColor: BKColor.lightBlue.darken3,
  125. NSAttributedString.Key.font: RobotoFont.regular(with: 14)
  126. ], range: (fullText as NSString).range(of: highlightText))
  127. let label = UILabel()
  128. label.attributedText = attrStr
  129. label.numberOfLines = 0
  130. view.addSubview(label)
  131. label.snp.makeConstraints { make in
  132. make.left.equalTo(12)
  133. make.right.equalTo(-12)
  134. make.top.equalTo(12)
  135. }
  136. label.isUserInteractionEnabled = true
  137. label.addGestureRecognizer(UITapGestureRecognizer())
  138. label.gestureRecognizers?.first?.rx.event.subscribe(onNext: { _ in
  139. UIApplication.shared.open(URL(string: "https://convertio.co/mp3-caf/")!)
  140. }).disposed(by: label.rx.disposeBag)
  141. return view
  142. }
  143. }
  144. extension SoundsViewController: UIDocumentPickerDelegate {
  145. /// 选择 caf 文件
  146. func pickerSoundFile() {
  147. if #available(iOS 14.0, *) {
  148. let types = UTType.types(tag: "caf",
  149. tagClass: UTTagClass.filenameExtension,
  150. conformingTo: nil)
  151. let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: types)
  152. documentPicker.delegate = self
  153. documentPicker.allowsMultipleSelection = false
  154. documentPicker.modalPresentationStyle = .pageSheet
  155. self.present(documentPicker, animated: true, completion: nil)
  156. } else {
  157. self.showSnackbar(text: "Requires iOS 14")
  158. }
  159. }
  160. // 文件选择完成回调
  161. func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
  162. guard let url = urls.first else { return }
  163. let canAccessingResource = url.startAccessingSecurityScopedResource()
  164. guard canAccessingResource else { return }
  165. let fileCoordinator = NSFileCoordinator()
  166. let err = NSErrorPointer(nilLiteral: ())
  167. fileCoordinator.coordinate(readingItemAt: url, error: err) { url in
  168. self.importSoundActionRelay.accept(url)
  169. }
  170. url.stopAccessingSecurityScopedResource()
  171. }
  172. }