NCShareAdvancePermission.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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. protocol NCShareDetail {
  13. var share: TableShareable! { get }
  14. }
  15. extension NCShareDetail where Self: UIViewController {
  16. func setNavigationTitle() {
  17. title = NSLocalizedString("_share_", comment: "") + " – "
  18. if share.shareType == 0 {
  19. title! += share.shareWithDisplayname.isEmpty ? share.shareWith : share.shareWithDisplayname
  20. } else {
  21. title! += share.label.isEmpty ? NSLocalizedString("_share_link_", comment: "") : share.label
  22. }
  23. }
  24. }
  25. class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareDetail {
  26. func dismissShareAdvanceView(shouldSave: Bool) {
  27. defer { navigationController?.popViewController(animated: true) }
  28. guard shouldSave else { return }
  29. if NCManageDatabase.shared.getTableShare(account: share.account, idShare: share.idShare) == nil {
  30. networking?.createShare(option: share, metadata: metadata)
  31. } else {
  32. networking?.updateShare(option: share)
  33. }
  34. }
  35. var share: TableShareable!
  36. var metadata: tableMetadata!
  37. var shareConfig: ShareConfig!
  38. var networking: NCShareNetworking?
  39. override func viewDidLoad() {
  40. super.viewDidLoad()
  41. self.shareConfig = ShareConfig(isDirectory: metadata.directory, share: share)
  42. self.setNavigationTitle()
  43. }
  44. override func viewWillLayoutSubviews() {
  45. super.viewWillLayoutSubviews()
  46. guard tableView.tableHeaderView == nil, tableView.tableFooterView == nil else { return }
  47. setupHeaderView()
  48. setupFooterView()
  49. }
  50. func setupFooterView() {
  51. guard let footerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionFooter", owner: self, options: nil)?.first as? NCShareAdvancePermissionFooter) else { return }
  52. footerView.setupUI(with: share, delegate: self)
  53. footerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 100)
  54. tableView.tableFooterView = footerView
  55. tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0)
  56. footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
  57. footerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
  58. footerView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
  59. footerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
  60. }
  61. func setupHeaderView() {
  62. guard let headerView = (Bundle.main.loadNibNamed("NCShareAdvancePermissionHeader", owner: self, options: nil)?.first as? NCShareAdvancePermissionHeader) else { return }
  63. headerView.setupUI(with: metadata)
  64. headerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 200)
  65. tableView.tableHeaderView = headerView
  66. headerView.translatesAutoresizingMaskIntoConstraints = false
  67. headerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
  68. headerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
  69. }
  70. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  71. if section == 0 { return NSLocalizedString("_advanced_", comment: "") }
  72. else if section == 1 { return NSLocalizedString("_misc_", comment: "") }
  73. else { return nil }
  74. }
  75. override func numberOfSections(in tableView: UITableView) -> Int {
  76. return 2
  77. }
  78. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  79. if section == 0 { return shareConfig.permissions.count }
  80. else if section == 1 { return shareConfig.advanced.count }
  81. else { return 0 }
  82. }
  83. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  84. guard let cell = shareConfig.cellFor(indexPath: indexPath) else { return UITableViewCell() }
  85. if let cell = cell as? DatePickerTableViewCell {
  86. cell.onReload = tableView.reloadData
  87. }
  88. return cell
  89. }
  90. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  91. tableView.deselectRow(at: indexPath, animated: true)
  92. guard let cellConfig = shareConfig.config(for: indexPath) else { return }
  93. if let cellConfig = cellConfig as? Advanced {
  94. switch cellConfig {
  95. case .hideDownload:
  96. share.hideDownload.toggle()
  97. tableView.reloadData()
  98. case .expirationDate:
  99. let cell = tableView.cellForRow(at: indexPath) as? DatePickerTableViewCell
  100. cell?.textField.becomeFirstResponder()
  101. case .password:
  102. guard share.password.isEmpty else {
  103. share.password = ""
  104. tableView.reloadData()
  105. return
  106. }
  107. let alertController = UIAlertController.withTextField(titleKey: "_enforce_password_protection_") { textField in
  108. textField.placeholder = NSLocalizedString("_password_", comment: "")
  109. textField.isSecureTextEntry = true
  110. } completion: { password in
  111. self.share.password = password ?? ""
  112. tableView.reloadData()
  113. }
  114. self.present(alertController, animated: true)
  115. case .note:
  116. let storyboard = UIStoryboard(name: "NCShare", bundle: nil)
  117. guard let viewNewUserComment = storyboard.instantiateViewController(withIdentifier: "NCShareNewUserAddComment") as? NCShareNewUserAddComment else { return }
  118. viewNewUserComment.metadata = self.metadata
  119. viewNewUserComment.share = self.share
  120. viewNewUserComment.onDismiss = tableView.reloadData
  121. self.navigationController?.pushViewController(viewNewUserComment, animated: true)
  122. case .label:
  123. let alertController = UIAlertController.withTextField(titleKey: "_share_link_name_") { textField in
  124. textField.placeholder = cellConfig.title
  125. textField.text = self.share.label
  126. } completion: { newValue in
  127. self.share.label = newValue ?? ""
  128. self.setNavigationTitle()
  129. tableView.reloadData()
  130. }
  131. self.present(alertController, animated: true)
  132. }
  133. } else {
  134. cellConfig.didSelect(for: share)
  135. tableView.reloadData()
  136. }
  137. }
  138. }
  139. protocol ShareCellConfig {
  140. var title: String { get }
  141. func getCell(for share: TableShareable) -> UITableViewCell
  142. func didSelect(for share: TableShareable)
  143. }
  144. protocol ToggleCellConfig: ShareCellConfig {
  145. func isOn(for share: TableShareable) -> Bool
  146. func didChange(_ share: TableShareable, to newValue: Bool)
  147. }
  148. extension ToggleCellConfig {
  149. func getCell(for share: TableShareable) -> UITableViewCell {
  150. return ToggleCell(isOn: isOn(for: share))
  151. }
  152. func didSelect(for share: TableShareable) {
  153. didChange(share, to: !isOn(for: share))
  154. }
  155. }
  156. protocol Permission: ToggleCellConfig {
  157. static var forDirectory: [Self] { get }
  158. static var forFile: [Self] { get }
  159. }
  160. enum UserPermission: CaseIterable, Permission {
  161. var permissionBitFlag: Int {
  162. switch self {
  163. case .reshare: return NCGlobal.shared.permissionShareShare
  164. case .edit: return NCGlobal.shared.permissionUpdateShare
  165. case .create: return NCGlobal.shared.permissionCreateShare
  166. case .delete: return NCGlobal.shared.permissionDeleteShare
  167. }
  168. }
  169. func didChange(_ share: TableShareable, to newValue: Bool) {
  170. share.permissions ^= permissionBitFlag
  171. }
  172. func isOn(for share: TableShareable) -> Bool {
  173. return (share.permissions & permissionBitFlag) != 0
  174. }
  175. case reshare, edit, create, delete
  176. static let forDirectory: [UserPermission] = UserPermission.allCases
  177. static let forFile: [UserPermission] = [.reshare, .edit]
  178. var title: String {
  179. switch self {
  180. case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "")
  181. case .edit: return NSLocalizedString("_share_can_change_", comment: "")
  182. case .create: return NSLocalizedString("_share_can_create_", comment: "")
  183. case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
  184. }
  185. }
  186. }
  187. enum LinkPermission: Permission {
  188. func didChange(_ share: TableShareable, to newValue: Bool) {
  189. guard self != .allowEdit else {
  190. // file
  191. share.permissions = CCUtility.getPermissionsValue(
  192. byCanEdit: newValue,
  193. andCanCreate: newValue,
  194. andCanChange: newValue,
  195. andCanDelete: newValue,
  196. andCanShare: false,
  197. andIsFolder: false)
  198. return
  199. }
  200. // can't deselect, only change
  201. guard newValue == true else { return }
  202. switch self {
  203. case .allowEdit: return
  204. case .viewOnly:
  205. share.permissions = CCUtility.getPermissionsValue(
  206. byCanEdit: false,
  207. andCanCreate: false,
  208. andCanChange: false,
  209. andCanDelete: false,
  210. andCanShare: false,
  211. andIsFolder: true)
  212. case .uploadEdit:
  213. share.permissions = CCUtility.getPermissionsValue(
  214. byCanEdit: true,
  215. andCanCreate: true,
  216. andCanChange: true,
  217. andCanDelete: true,
  218. andCanShare: false,
  219. andIsFolder: true)
  220. case .fileDrop:
  221. share.permissions = NCGlobal.shared.permissionCreateShare
  222. }
  223. }
  224. func isOn(for share: TableShareable) -> Bool {
  225. switch self {
  226. case .allowEdit: return CCUtility.isAnyPermission(toEdit: share.permissions)
  227. case .viewOnly: return !CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  228. case .uploadEdit: return CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  229. case .fileDrop: return share.permissions == NCGlobal.shared.permissionCreateShare
  230. }
  231. }
  232. var title: String {
  233. switch self {
  234. case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "")
  235. case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "")
  236. case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "")
  237. case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "")
  238. }
  239. }
  240. case allowEdit, viewOnly, uploadEdit, fileDrop
  241. static let forDirectory: [LinkPermission] = [.viewOnly, .uploadEdit, .fileDrop]
  242. static let forFile: [LinkPermission] = [.allowEdit]
  243. }
  244. enum Advanced: CaseIterable, ShareCellConfig {
  245. func didSelect(for share: TableShareable) {
  246. switch self {
  247. case .hideDownload: share.hideDownload.toggle()
  248. case .expirationDate: return
  249. case .password: return
  250. case .note: return
  251. case .label: return
  252. }
  253. }
  254. func getCell(for share: TableShareable) -> UITableViewCell {
  255. switch self {
  256. case .hideDownload:
  257. return ToggleCell(isOn: share.hideDownload)
  258. case .expirationDate:
  259. return DatePickerTableViewCell(share: share)
  260. case .password: return ToggleCell(isOn: !share.password.isEmpty, customIcons: ("lock", "lock.open"))
  261. case .note:
  262. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareNote")
  263. cell.detailTextLabel?.text = share.note
  264. cell.accessoryType = .disclosureIndicator
  265. return cell
  266. case .label:
  267. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel")
  268. cell.detailTextLabel?.text = share.label
  269. return cell
  270. }
  271. }
  272. var title: String {
  273. switch self {
  274. case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "")
  275. case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "")
  276. case .password: return NSLocalizedString("_share_password_protect_", comment: "")
  277. case .note: return NSLocalizedString("_share_note_recipient_", comment: "")
  278. case .label: return NSLocalizedString("_share_link_name_", comment: "")
  279. }
  280. }
  281. case label, hideDownload, expirationDate, password, note
  282. static let forLink: [Advanced] = Advanced.allCases
  283. static let forUser: [Advanced] = [.expirationDate, .note]
  284. }
  285. struct ShareConfig {
  286. let permissions: [Permission]
  287. let advanced: [Advanced]
  288. let share: TableShareable
  289. init(isDirectory: Bool, share: TableShareable) {
  290. self.share = share
  291. let type: Permission.Type = share.shareType == 3 ? LinkPermission.self : UserPermission.self
  292. self.permissions = isDirectory ? type.forDirectory : type.forFile
  293. self.advanced = share.shareType == 3 ? Advanced.forLink : Advanced.forUser
  294. }
  295. func cellFor(indexPath: IndexPath) -> UITableViewCell? {
  296. let cellConfig = config(for: indexPath)
  297. let cell = cellConfig?.getCell(for: share)
  298. cell?.textLabel?.text = cellConfig?.title
  299. return cell
  300. }
  301. func didSelectRow(at indexPath: IndexPath) {
  302. let cellConfig = config(for: indexPath)
  303. cellConfig?.didSelect(for: share)
  304. }
  305. func config(for indexPath: IndexPath) -> ShareCellConfig? {
  306. if indexPath.section == 0, indexPath.row < permissions.count {
  307. return permissions[indexPath.row]
  308. } else if indexPath.section == 1, indexPath.row < advanced.count {
  309. return advanced[indexPath.row]
  310. } else { return nil }
  311. }
  312. }
  313. class ToggleCell: UITableViewCell {
  314. typealias CustomToggleIcon = (onIconName: String?, offIconName: String?)
  315. init(isOn: Bool, customIcons: CustomToggleIcon? = nil) {
  316. super.init(style: .default, reuseIdentifier: "toggleCell")
  317. guard let customIcons = customIcons,
  318. let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else {
  319. self.accessoryType = isOn ? .checkmark : .none
  320. return
  321. }
  322. let image = NCUtility.shared.loadImage(named: iconName, color: NCBrandColor.shared.brandElement)
  323. self.accessoryView = UIImageView(image: image)
  324. }
  325. required init?(coder: NSCoder) {
  326. fatalError("init(coder:) has not been implemented")
  327. }
  328. }
  329. extension DateFormatter {
  330. static let shareExpDate: DateFormatter = {
  331. let dateFormatter = DateFormatter()
  332. dateFormatter.formatterBehavior = .behavior10_4
  333. dateFormatter.dateStyle = .medium
  334. return dateFormatter
  335. }()
  336. }
  337. open class DatePickerTableViewCell: UITableViewCell {
  338. let picker = UIDatePicker()
  339. let textField = UITextField()
  340. var onReload: (() -> Void)?
  341. init(share: TableShareable) {
  342. super.init(style: .value1, reuseIdentifier: "shareExpDate")
  343. picker.datePickerMode = .date
  344. picker.minimumDate = Date()
  345. if #available(iOS 13.4, *) {
  346. picker.preferredDatePickerStyle = .wheels
  347. }
  348. picker.action(for: .valueChanged) { datePicker in
  349. guard let datePicker = datePicker as? UIDatePicker else { return }
  350. self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date)
  351. }
  352. accessoryView = textField
  353. let toolbar = UIToolbar()
  354. toolbar.sizeToFit()
  355. let doneButton = UIBarButtonItem(title: "_done_", style: .done) {
  356. self.resignFirstResponder()
  357. share.expirationDate = self.picker.date as NSDate
  358. self.onReload?()
  359. }
  360. let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
  361. let cancelButton = UIBarButtonItem(title: NSLocalizedString("_clear_", comment: ""), style: .plain) {
  362. self.resignFirstResponder()
  363. share.expirationDate = nil
  364. self.onReload?()
  365. }
  366. toolbar.setItems([cancelButton, spaceButton, doneButton], animated: false)
  367. textField.inputAccessoryView = toolbar
  368. textField.inputView = picker
  369. if let expDate = share.expirationDate {
  370. detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date)
  371. }
  372. }
  373. required public init?(coder: NSCoder) {
  374. fatalError("init(coder:) has not been implemented")
  375. }
  376. }