NCShareAdvancePermission.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. //
  2. // NCShareAdvancePermission.swift
  3. // Nextcloud
  4. //
  5. // Created by T-systems on 09/08/21.
  6. // Copyright © 2021 Marino Faggiana. All rights reserved.
  7. //
  8. import UIKit
  9. import NCCommunication
  10. import SVGKit
  11. import CloudKit
  12. class NCShareAdvancePermission: UITableViewController {
  13. var share: tableShare!
  14. var metadata: tableMetadata!
  15. var shareConfig: ShareConfig!
  16. override func viewDidLoad() {
  17. super.viewDidLoad()
  18. self.shareConfig = ShareConfig(isDirectory: metadata.directory, share: share)
  19. }
  20. override func viewWillLayoutSubviews() {
  21. super.viewWillLayoutSubviews()
  22. guard tableView.tableHeaderView == nil, tableView.tableFooterView == nil else { return }
  23. setupHeaderView()
  24. setupFooterView()
  25. }
  26. @objc func cancelClicked() {
  27. navigationController?.popViewController(animated: true)
  28. }
  29. @objc func nextClicked() {
  30. }
  31. func setupFooterView() {
  32. guard let footerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionFooter", owner: self, options: nil)?.first as? NCShareAdvancePermissionFooter) else { return }
  33. footerView.backgroundColor = .clear
  34. footerView.addShadow(location: .top)
  35. footerView.buttonCancel.addTarget(self, action: #selector(cancelClicked), for: .touchUpInside)
  36. footerView.buttonCancel.setTitle(NSLocalizedString("_cancel_", comment: ""), for: .normal)
  37. footerView.buttonCancel.layer.cornerRadius = 10
  38. footerView.buttonCancel.layer.masksToBounds = true
  39. footerView.buttonCancel.layer.borderWidth = 1
  40. if NCManageDatabase.shared.getTableShare(account: share.account, idShare: share.idShare) == nil {
  41. footerView.buttonNext.setTitle(NSLocalizedString("_next_", comment: ""), for: .normal)
  42. } else {
  43. footerView.buttonNext.setTitle(NSLocalizedString("_apply_changes_", comment: ""), for: .normal)
  44. }
  45. footerView.buttonNext.layer.cornerRadius = 10
  46. footerView.buttonNext.layer.masksToBounds = true
  47. footerView.buttonNext.backgroundColor = NCBrandColor.shared.brand
  48. footerView.buttonNext.addTarget(self, action: #selector(nextClicked), for: .touchUpInside)
  49. footerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 100)
  50. tableView.tableFooterView = footerView
  51. tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0)
  52. footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
  53. footerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
  54. footerView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
  55. footerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
  56. }
  57. func setupHeaderView() {
  58. guard let headerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader) else { return }
  59. // headerView.backgroundColor = NCBrandColor.shared.secondarySystemBackground
  60. if FileManager.default.fileExists(atPath: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
  61. headerView.fullWidthImageView.image = NCUtility.shared.getImageMetadata(metadata, for: headerView.frame.height)
  62. headerView.fullWidthImageView.contentMode = .scaleAspectFill
  63. headerView.imageView.isHidden = true
  64. } else {
  65. if metadata!.directory {
  66. headerView.imageView.image = UIImage(named: "folder")
  67. } else if !metadata.iconName.isEmpty {
  68. headerView.imageView.image = UIImage(named: metadata.iconName)
  69. } else {
  70. headerView.imageView.image = UIImage(named: "file")
  71. }
  72. }
  73. headerView.favorite.setNeedsUpdateConstraints()
  74. headerView.favorite.layoutIfNeeded()
  75. headerView.fileName.text = self.metadata?.fileNameView
  76. headerView.fileName.textColor = NCBrandColor.shared.label
  77. // headerView.favorite.addTarget(self, action: #selector(favoriteClicked), for: .touchUpInside)
  78. if metadata.favorite {
  79. headerView.favorite.setImage(NCUtility.shared.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite, size: 24), for: .normal)
  80. } else {
  81. headerView.favorite.setImage(NCUtility.shared.loadImage(named: "star.fill", color: NCBrandColor.shared.systemGray, size: 24), for: .normal)
  82. }
  83. headerView.info.textColor = NCBrandColor.shared.secondaryLabel
  84. headerView.info.text = CCUtility.transformedSize(metadata.size) + ", " + CCUtility.dateDiff(metadata.date as Date)
  85. headerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 200)
  86. tableView.tableHeaderView = headerView
  87. headerView.translatesAutoresizingMaskIntoConstraints = false
  88. headerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
  89. headerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
  90. }
  91. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  92. if section == 0 { return NSLocalizedString("_advanced_", comment: "") }
  93. else if section == 1 { return NSLocalizedString("_misc_", comment: "") }
  94. else { return nil }
  95. }
  96. override func numberOfSections(in tableView: UITableView) -> Int {
  97. return 2
  98. }
  99. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  100. if section == 0 { return shareConfig.permissions.count }
  101. else if section == 1 { return shareConfig.advanced.count }
  102. else { return 0 }
  103. }
  104. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  105. guard let cell = shareConfig.cellFor(indexPath: indexPath) else { return UITableViewCell() }
  106. if let cell = cell as? DatePickerTableViewCell {
  107. cell.onReload = tableView.reloadData
  108. }
  109. return cell
  110. }
  111. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  112. tableView.deselectRow(at: indexPath, animated: true)
  113. guard let cellConfig = shareConfig.config(for: indexPath) else { return }
  114. if let cellConfig = cellConfig as? ToggleCellConfig {
  115. cellConfig.didSelect(for: share)
  116. tableView.reloadData()
  117. } else if let cellConfig = cellConfig as? Advanced {
  118. switch cellConfig {
  119. case .hideDownload:
  120. share.hideDownload.toggle()
  121. tableView.reloadData()
  122. case .expirationDate:
  123. let cell = tableView.cellForRow(at: indexPath) as? DatePickerTableViewCell
  124. cell?.textField.becomeFirstResponder()
  125. case .password:
  126. guard share.password.isEmpty else {
  127. share.password = ""
  128. tableView.reloadData()
  129. return
  130. }
  131. let alertController = UIAlertController(title: NSLocalizedString("_enforce_password_protection_", comment: ""), message: "", preferredStyle: .alert)
  132. alertController.addTextField { textField in
  133. textField.isSecureTextEntry = true
  134. }
  135. alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { _ in })
  136. let okAction = UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default) { _ in
  137. let password = alertController.textFields?.first?.text
  138. self.share.password = password ?? ""
  139. tableView.reloadData()
  140. }
  141. alertController.addAction(okAction)
  142. self.present(alertController, animated: true)
  143. case .note: break
  144. // TODO: Pushnote VC
  145. }
  146. } // else: unkown cell
  147. }
  148. }
  149. protocol ShareCellConfig {
  150. var title: String { get }
  151. func getCell(for share: tableShare) -> UITableViewCell
  152. func didSelect(for share: tableShare)
  153. }
  154. protocol ToggleCellConfig: ShareCellConfig {
  155. func isOn(for share: tableShare) -> Bool
  156. func didChange(_ share: tableShare, to newValue: Bool)
  157. }
  158. extension ToggleCellConfig {
  159. func getCell(for share: tableShare) -> UITableViewCell {
  160. return ToggleCell(isOn: isOn(for: share))
  161. }
  162. func didSelect(for share: tableShare) {
  163. didChange(share, to: isOn(for: share))
  164. }
  165. }
  166. protocol Permission: ToggleCellConfig {
  167. static var forDirectory: [Self] { get }
  168. static var forFile: [Self] { get }
  169. }
  170. enum UserPermission: CaseIterable, Permission {
  171. func didChange(_ share: tableShare, to newValue: Bool) {
  172. }
  173. func isOn(for share: tableShare) -> Bool {
  174. switch self {
  175. case .reshare: return CCUtility.isPermission(toCanShare: share.permissions)
  176. case .edit: return CCUtility.isPermission(toCanChange: share.permissions)
  177. case .create: return CCUtility.isPermission(toCanCreate: share.permissions)
  178. case .delete: return CCUtility.isPermission(toCanDelete: share.permissions)
  179. }
  180. }
  181. case reshare, edit, create, delete
  182. static let forDirectory: [UserPermission] = UserPermission.allCases
  183. static let forFile: [UserPermission] = [.reshare, .edit]
  184. var title: String {
  185. switch self {
  186. case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "")
  187. case .edit: return NSLocalizedString("_share_can_change_", comment: "")
  188. case .create: return NSLocalizedString("_share_can_create_", comment: "")
  189. case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
  190. }
  191. }
  192. }
  193. enum LinkPermission: Permission {
  194. func didChange(_ share: tableShare, to newValue: Bool) {
  195. }
  196. func isOn(for share: tableShare) -> Bool {
  197. switch self {
  198. case .allowEdit: return CCUtility.isAnyPermission(toEdit: share.permissions)
  199. case .viewOnly: return !CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  200. case .uploadEdit: return CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  201. case .fileDrop: return share.permissions == NCGlobal.shared.permissionCreateShare
  202. }
  203. }
  204. var title: String {
  205. switch self {
  206. case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "")
  207. case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "")
  208. case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "")
  209. case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "")
  210. }
  211. }
  212. case allowEdit, viewOnly, uploadEdit, fileDrop
  213. static let forDirectory: [LinkPermission] = [.viewOnly, .uploadEdit, .fileDrop]
  214. static let forFile: [LinkPermission] = [.allowEdit]
  215. }
  216. enum Advanced: CaseIterable, ShareCellConfig {
  217. func didSelect(for share: tableShare) {
  218. switch self {
  219. case .hideDownload: share.hideDownload.toggle()
  220. case .expirationDate: return
  221. case .password: return
  222. case .note: return
  223. }
  224. }
  225. func getCell(for share: tableShare) -> UITableViewCell {
  226. switch self {
  227. case .hideDownload:
  228. return ToggleCell(isOn: share.hideDownload)
  229. case .expirationDate:
  230. return DatePickerTableViewCell(share: share)
  231. case .password: return ToggleCell(isOn: !share.password.isEmpty)
  232. case .note:
  233. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareNote")
  234. cell.detailTextLabel?.text = share.note
  235. cell.accessoryType = .disclosureIndicator
  236. return cell
  237. }
  238. }
  239. var title: String {
  240. switch self {
  241. case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "")
  242. case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "")
  243. case .password: return NSLocalizedString("_share_password_protect_", comment: "")
  244. case .note: return NSLocalizedString("_share_note_recipient_", comment: "")
  245. }
  246. }
  247. case hideDownload, expirationDate, password, note
  248. static let forLink: [Advanced] = Advanced.allCases
  249. static let forUser: [Advanced] = [.expirationDate, .note]
  250. }
  251. struct ShareConfig {
  252. let permissions: [Permission]
  253. let advanced: [Advanced]
  254. let share: tableShare
  255. init(isDirectory: Bool, share: tableShare) {
  256. self.share = share
  257. let type: Permission.Type = share.shareType == 3 ? LinkPermission.self : UserPermission.self
  258. self.permissions = isDirectory ? type.forDirectory : type.forFile
  259. self.advanced = share.shareType == 3 ? Advanced.forLink : Advanced.forUser
  260. }
  261. func cellFor(indexPath: IndexPath) -> UITableViewCell? {
  262. let cellConfig = config(for: indexPath)
  263. let cell = cellConfig?.getCell(for: share)
  264. cell?.textLabel?.text = cellConfig?.title
  265. return cell
  266. }
  267. func didSelectRow(at indexPath: IndexPath) {
  268. let cellConfig = config(for: indexPath)
  269. cellConfig?.didSelect(for: share)
  270. }
  271. func config(for indexPath: IndexPath) -> ShareCellConfig? {
  272. if indexPath.section == 0, indexPath.row < permissions.count {
  273. return permissions[indexPath.row]
  274. } else if indexPath.section == 1, indexPath.row < advanced.count {
  275. return advanced[indexPath.row]
  276. } else { return nil }
  277. }
  278. }
  279. class ToggleCell: UITableViewCell {
  280. init(isOn: Bool) {
  281. super.init(style: .default, reuseIdentifier: "toggleCell")
  282. if isOn {
  283. self.accessoryType = .checkmark
  284. } else {
  285. self.accessoryType = .none
  286. }
  287. }
  288. required init?(coder: NSCoder) {
  289. fatalError("init(coder:) has not been implemented")
  290. }
  291. }
  292. extension DateFormatter {
  293. static let shareExpDate: DateFormatter = {
  294. let dateFormatter = DateFormatter()
  295. dateFormatter.formatterBehavior = .behavior10_4
  296. dateFormatter.dateStyle = .medium
  297. return dateFormatter
  298. }()
  299. }
  300. open class DatePickerTableViewCell: UITableViewCell {
  301. let picker = UIDatePicker()
  302. let textField = UITextField()
  303. var onReload: (() -> Void)?
  304. init(share: tableShare) {
  305. super.init(style: .value1, reuseIdentifier: "shareExpDate")
  306. picker.datePickerMode = .date
  307. if #available(iOS 13.4, *) {
  308. picker.preferredDatePickerStyle = .wheels
  309. }
  310. picker.action(for: .valueChanged) { datePicker in
  311. guard let datePicker = datePicker as? UIDatePicker else { return }
  312. self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date)
  313. }
  314. accessoryView = textField
  315. let toolbar = UIToolbar()
  316. toolbar.sizeToFit()
  317. let doneButton = UIBarButtonItem(title: "_done_", style: .done) {
  318. self.resignFirstResponder()
  319. share.date = self.picker.date as NSDate
  320. self.onReload?()
  321. }
  322. let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
  323. let cancelButton = UIBarButtonItem(title: NSLocalizedString("_clear_", comment: ""), style: .plain) {
  324. self.resignFirstResponder()
  325. share.date = nil
  326. self.onReload?()
  327. }
  328. toolbar.setItems([cancelButton, spaceButton, doneButton], animated: false)
  329. textField.inputAccessoryView = toolbar
  330. textField.inputView = picker
  331. if let expDate = share.date {
  332. detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date)
  333. }
  334. }
  335. required public init?(coder: NSCoder) {
  336. fatalError("init(coder:) has not been implemented")
  337. }
  338. }