MessageSettingsViewController.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. //
  2. // MessageSettingsViewController.swift
  3. // Bark
  4. //
  5. // Created by huangfeng on 2020/5/28.
  6. // Copyright © 2020 Fin. All rights reserved.
  7. //
  8. import Material
  9. import RxCocoa
  10. import RxDataSources
  11. import RxSwift
  12. import UIKit
  13. import UniformTypeIdentifiers
  14. class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel>, UIDocumentPickerDelegate {
  15. let tableView: UITableView = {
  16. let tableView = UITableView()
  17. tableView.separatorStyle = .none
  18. tableView.backgroundColor = BKColor.background.primary
  19. tableView.register(LabelCell.self, forCellReuseIdentifier: "\(LabelCell.self)")
  20. tableView.register(iCloudStatusCell.self, forCellReuseIdentifier: "\(iCloudStatusCell.self)")
  21. tableView.register(ArchiveSettingCell.self, forCellReuseIdentifier: "\(ArchiveSettingCell.self)")
  22. tableView.register(DetailTextCell.self, forCellReuseIdentifier: "\(DetailTextCell.self)")
  23. tableView.register(MutableTextCell.self, forCellReuseIdentifier: "\(MutableTextCell.self)")
  24. tableView.register(SpacerCell.self, forCellReuseIdentifier: "\(SpacerCell.self)")
  25. return tableView
  26. }()
  27. override func makeUI() {
  28. self.title = NSLocalizedString("settings")
  29. self.view.addSubview(tableView)
  30. tableView.snp.makeConstraints { make in
  31. make.edges.equalToSuperview()
  32. }
  33. }
  34. /// 导入、导出操作枚举
  35. enum BackupOrRestoreActionEnum {
  36. case export, `import`(data: Data)
  37. }
  38. /// UIDocumentPickerDelegate delegate
  39. func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
  40. guard let url = urls.first else { return }
  41. let canAccessingResource = url.startAccessingSecurityScopedResource()
  42. guard canAccessingResource else { return }
  43. let fileCoordinator = NSFileCoordinator()
  44. let err = NSErrorPointer(nilLiteral: ())
  45. fileCoordinator.coordinate(readingItemAt: url, error: err) { url in
  46. if let data = try? Data(contentsOf: url) {
  47. self.backupOrRestoreActionRelay.accept(.import(data: data))
  48. }
  49. }
  50. url.stopAccessingSecurityScopedResource()
  51. }
  52. /// 导入、导出事件
  53. let backupOrRestoreActionRelay = PublishRelay<BackupOrRestoreActionEnum>()
  54. /// 生成导入导出事件
  55. func getBackupOrRestoreAction() -> (Driver<Void>, Driver<Data>) {
  56. let backupOrRestoreAction = self.tableView.rx
  57. // .modelSelected(MessageSettingItem.self)
  58. .itemSelected
  59. .filter { indexPath in
  60. guard let viewModel: MessageSettingItem = try? self.tableView.rx.model(at: indexPath) else {
  61. return false;
  62. }
  63. if case MessageSettingItem.backup = viewModel {
  64. return true
  65. }
  66. return false
  67. }
  68. .flatMapLatest { [weak self] indexPath in
  69. guard let strongSelf = self else {
  70. return Observable<BackupOrRestoreActionEnum>.empty()
  71. }
  72. let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  73. alertController.addAction(UIAlertAction(title: NSLocalizedString("export"), style: .default, handler: { _ in
  74. strongSelf.backupOrRestoreActionRelay.accept(.export)
  75. }))
  76. alertController.addAction(UIAlertAction(title: NSLocalizedString("import"), style: .default, handler: { [weak self] _ in
  77. if #available(iOS 14.0, *) {
  78. let supportedType: [UTType] = [UTType.json]
  79. let pickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedType, asCopy: false)
  80. pickerViewController.delegate = self
  81. self?.present(pickerViewController, animated: true, completion: nil)
  82. }
  83. }))
  84. alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
  85. if UIDevice.current.userInterfaceIdiom == .pad {
  86. alertController.modalPresentationStyle = .popover
  87. if let cell = strongSelf.tableView.cellForRow(at: indexPath) {
  88. alertController.popoverPresentationController?.sourceView = strongSelf.tableView
  89. alertController.popoverPresentationController?.sourceRect = cell.frame
  90. }
  91. }
  92. strongSelf.present(alertController, animated: true, completion: nil)
  93. return strongSelf.backupOrRestoreActionRelay.asObservable()
  94. }
  95. let backupAction = backupOrRestoreAction
  96. .filter { action in
  97. if case .export = action { return true } else { return false }
  98. }
  99. .map { _ in () }
  100. .asDriver(onErrorDriveWith: .empty())
  101. let restoreAction = backupOrRestoreAction
  102. .compactMap { action in
  103. if case let .import(data) = action { return data } else { return nil }
  104. }
  105. .asDriver(onErrorDriveWith: .empty())
  106. return (backupAction, restoreAction)
  107. }
  108. override func bindViewModel() {
  109. let actions = getBackupOrRestoreAction()
  110. let output = viewModel.transform(
  111. input: MessageSettingsViewModel.Input(
  112. itemSelected: self.tableView.rx.modelSelected(MessageSettingItem.self).asDriver(),
  113. deviceToken: Client.shared.deviceToken.asDriver(),
  114. backupAction: actions.0,
  115. restoreAction: actions.1,
  116. viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:)))
  117. .map { _ in () },
  118. archiveSettingRelay: ArchiveSettingRelay.shared.isArchiveRelay
  119. )
  120. )
  121. let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, MessageSettingItem>> { _, tableView, _, item -> UITableViewCell in
  122. switch item {
  123. case let .label(text):
  124. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(LabelCell.self)") as? LabelCell {
  125. cell.textLabel?.text = text
  126. return cell
  127. }
  128. case .iCloudStatus:
  129. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(iCloudStatusCell.self)") {
  130. return cell
  131. }
  132. case let .backup(viewModel):
  133. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(MutableTextCell.self)") as? MutableTextCell {
  134. cell.textLabel?.textColor = BKColor.blue.darken1
  135. cell.bindViewModel(model: viewModel)
  136. return cell
  137. }
  138. case let .archiveSetting(viewModel):
  139. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ArchiveSettingCell.self)") as? ArchiveSettingCell {
  140. cell.bindViewModel(model: viewModel)
  141. return cell
  142. }
  143. case let .detail(title, text, textColor, _):
  144. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(DetailTextCell.self)") as? DetailTextCell {
  145. cell.textLabel?.text = title
  146. cell.detailTextLabel?.text = text
  147. cell.detailTextLabel?.textColor = textColor
  148. return cell
  149. }
  150. case let .deviceToken(viewModel):
  151. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(MutableTextCell.self)") as? MutableTextCell {
  152. cell.bindViewModel(model: viewModel)
  153. return cell
  154. }
  155. case let .spacer(height, color):
  156. if let cell = tableView.dequeueReusableCell(withIdentifier: "\(SpacerCell.self)") as? SpacerCell {
  157. cell.height = height
  158. cell.backgroundColor = color
  159. return cell
  160. }
  161. }
  162. return UITableViewCell()
  163. }
  164. // 设置项数据源
  165. output.settings
  166. .drive(tableView.rx.items(dataSource: dataSource))
  167. .disposed(by: rx.disposeBag)
  168. // 打开 URL 操作
  169. output.openUrl.drive { [weak self] url in
  170. self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
  171. }.disposed(by: rx.disposeBag)
  172. // 复制 deviceToken 操作
  173. output.copyDeviceToken.drive { [weak self] deviceToken in
  174. UIPasteboard.general.string = deviceToken
  175. self?.showSnackbar(text: NSLocalizedString("Copy"))
  176. }.disposed(by: rx.disposeBag)
  177. // 导出数据
  178. output.exportData.drive { [weak self] data in
  179. let fileManager = FileManager.default
  180. let tempDirectoryURL = fileManager.temporaryDirectory
  181. let fileName = "bark_messages_\(Date().formatString(format: "yyyy_MM_dd_HH_mm_ss")).json"
  182. let linkURL = tempDirectoryURL.appendingPathComponent(fileName)
  183. do {
  184. // 清空temp文件夹
  185. try fileManager
  186. .contentsOfDirectory(at: tempDirectoryURL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
  187. .forEach { file in
  188. try? fileManager.removeItem(atPath: file.path)
  189. }
  190. // 写入临时文件
  191. try data.write(to: linkURL)
  192. }
  193. catch {
  194. // Hope nothing happens
  195. }
  196. let activityController = UIActivityViewController(activityItems: [linkURL], applicationActivities: nil)
  197. if UIDevice.current.userInterfaceIdiom == .pad {
  198. activityController.popoverPresentationController?.sourceView = self?.view
  199. activityController.popoverPresentationController?.sourceRect = self?.view.frame ?? .zero
  200. }
  201. self?.navigationController?.present(activityController, animated: true, completion: nil)
  202. }.disposed(by: rx.disposeBag)
  203. }
  204. }