ShareCells.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. //
  2. // ShareCells.swift
  3. // Nextcloud
  4. //
  5. // Created by Henrik Storch on 18.03.22.
  6. // Copyright © 2022 Marino Faggiana. All rights reserved.
  7. //
  8. import UIKit
  9. protocol ShareCellConfig {
  10. var title: String { get }
  11. func getCell(for share: TableShareable) -> UITableViewCell
  12. func didSelect(for share: TableShareable)
  13. }
  14. protocol ToggleCellConfig: ShareCellConfig {
  15. func isOn(for share: TableShareable) -> Bool
  16. func didChange(_ share: TableShareable, to newValue: Bool)
  17. }
  18. extension ToggleCellConfig {
  19. func getCell(for share: TableShareable) -> UITableViewCell {
  20. return ToggleCell(isOn: isOn(for: share))
  21. }
  22. func didSelect(for share: TableShareable) {
  23. didChange(share, to: !isOn(for: share))
  24. }
  25. }
  26. protocol Permission: ToggleCellConfig {
  27. static var forDirectory: [Self] { get }
  28. static var forFile: [Self] { get }
  29. }
  30. enum UserPermission: CaseIterable, Permission {
  31. var permissionBitFlag: Int {
  32. switch self {
  33. case .reshare: return NCGlobal.shared.permissionShareShare
  34. case .edit: return NCGlobal.shared.permissionUpdateShare
  35. case .create: return NCGlobal.shared.permissionCreateShare
  36. case .delete: return NCGlobal.shared.permissionDeleteShare
  37. }
  38. }
  39. func didChange(_ share: TableShareable, to newValue: Bool) {
  40. share.permissions ^= permissionBitFlag
  41. }
  42. func isOn(for share: TableShareable) -> Bool {
  43. return (share.permissions & permissionBitFlag) != 0
  44. }
  45. case reshare, edit, create, delete
  46. static let forDirectory: [UserPermission] = UserPermission.allCases
  47. static let forFile: [UserPermission] = [.reshare, .edit]
  48. var title: String {
  49. switch self {
  50. case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "")
  51. case .edit: return NSLocalizedString("_share_can_change_", comment: "")
  52. case .create: return NSLocalizedString("_share_can_create_", comment: "")
  53. case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
  54. }
  55. }
  56. }
  57. enum LinkPermission: Permission {
  58. func didChange(_ share: TableShareable, to newValue: Bool) {
  59. guard self != .allowEdit else {
  60. // file
  61. share.permissions = CCUtility.getPermissionsValue(
  62. byCanEdit: newValue,
  63. andCanCreate: newValue,
  64. andCanChange: newValue,
  65. andCanDelete: newValue,
  66. andCanShare: false,
  67. andIsFolder: false)
  68. return
  69. }
  70. // can't deselect, only change
  71. guard newValue == true else { return }
  72. switch self {
  73. case .allowEdit: return
  74. case .viewOnly:
  75. share.permissions = CCUtility.getPermissionsValue(
  76. byCanEdit: false,
  77. andCanCreate: false,
  78. andCanChange: false,
  79. andCanDelete: false,
  80. andCanShare: false,
  81. andIsFolder: true)
  82. case .uploadEdit:
  83. share.permissions = CCUtility.getPermissionsValue(
  84. byCanEdit: true,
  85. andCanCreate: true,
  86. andCanChange: true,
  87. andCanDelete: true,
  88. andCanShare: false,
  89. andIsFolder: true)
  90. case .fileDrop:
  91. share.permissions = NCGlobal.shared.permissionCreateShare
  92. }
  93. }
  94. func isOn(for share: TableShareable) -> Bool {
  95. switch self {
  96. case .allowEdit: return CCUtility.isAnyPermission(toEdit: share.permissions)
  97. case .viewOnly: return !CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  98. case .uploadEdit: return CCUtility.isAnyPermission(toEdit: share.permissions) && share.permissions != NCGlobal.shared.permissionCreateShare
  99. case .fileDrop: return share.permissions == NCGlobal.shared.permissionCreateShare
  100. }
  101. }
  102. var title: String {
  103. switch self {
  104. case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "")
  105. case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "")
  106. case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "")
  107. case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "")
  108. }
  109. }
  110. case allowEdit, viewOnly, uploadEdit, fileDrop
  111. static let forDirectory: [LinkPermission] = [.viewOnly, .uploadEdit, .fileDrop]
  112. static let forFile: [LinkPermission] = [.allowEdit]
  113. }
  114. enum Advanced: CaseIterable, ShareCellConfig {
  115. func didSelect(for share: TableShareable) {
  116. switch self {
  117. case .hideDownload: share.hideDownload.toggle()
  118. case .expirationDate: return
  119. case .password: return
  120. case .note: return
  121. case .label: return
  122. }
  123. }
  124. func getCell(for share: TableShareable) -> UITableViewCell {
  125. switch self {
  126. case .hideDownload:
  127. return ToggleCell(isOn: share.hideDownload)
  128. case .expirationDate:
  129. return DatePickerTableViewCell(share: share)
  130. case .password: return ToggleCell(isOn: !share.password.isEmpty, customIcons: ("lock", "lock.open"))
  131. case .note:
  132. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareNote")
  133. cell.detailTextLabel?.text = share.note
  134. cell.accessoryType = .disclosureIndicator
  135. return cell
  136. case .label:
  137. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel")
  138. cell.detailTextLabel?.text = share.label
  139. return cell
  140. }
  141. }
  142. var title: String {
  143. switch self {
  144. case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "")
  145. case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "")
  146. case .password: return NSLocalizedString("_share_password_protect_", comment: "")
  147. case .note: return NSLocalizedString("_share_note_recipient_", comment: "")
  148. case .label: return NSLocalizedString("_share_link_name_", comment: "")
  149. }
  150. }
  151. case label, hideDownload, expirationDate, password, note
  152. static let forLink: [Advanced] = Advanced.allCases
  153. static let forUser: [Advanced] = [.expirationDate, .note]
  154. }
  155. struct ShareConfig {
  156. let permissions: [Permission]
  157. let advanced: [Advanced]
  158. let share: TableShareable
  159. init(isDirectory: Bool, share: TableShareable) {
  160. self.share = share
  161. let type: Permission.Type = share.shareType == NCShareCommon.shared.SHARE_TYPE_LINK ? LinkPermission.self : UserPermission.self
  162. self.permissions = isDirectory ? type.forDirectory : type.forFile
  163. self.advanced = share.shareType == NCShareCommon.shared.SHARE_TYPE_LINK ? Advanced.forLink : Advanced.forUser
  164. }
  165. func cellFor(indexPath: IndexPath) -> UITableViewCell? {
  166. let cellConfig = config(for: indexPath)
  167. let cell = cellConfig?.getCell(for: share)
  168. cell?.textLabel?.text = cellConfig?.title
  169. return cell
  170. }
  171. func didSelectRow(at indexPath: IndexPath) {
  172. let cellConfig = config(for: indexPath)
  173. cellConfig?.didSelect(for: share)
  174. }
  175. func config(for indexPath: IndexPath) -> ShareCellConfig? {
  176. if indexPath.section == 0, indexPath.row < permissions.count {
  177. return permissions[indexPath.row]
  178. } else if indexPath.section == 1, indexPath.row < advanced.count {
  179. return advanced[indexPath.row]
  180. } else { return nil }
  181. }
  182. }
  183. class ToggleCell: UITableViewCell {
  184. typealias CustomToggleIcon = (onIconName: String?, offIconName: String?)
  185. init(isOn: Bool, customIcons: CustomToggleIcon? = nil) {
  186. super.init(style: .default, reuseIdentifier: "toggleCell")
  187. guard let customIcons = customIcons,
  188. let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else {
  189. self.accessoryType = isOn ? .checkmark : .none
  190. return
  191. }
  192. let image = NCUtility.shared.loadImage(named: iconName, color: NCBrandColor.shared.brandElement)
  193. self.accessoryView = UIImageView(image: image)
  194. }
  195. required init?(coder: NSCoder) {
  196. fatalError("init(coder:) has not been implemented")
  197. }
  198. }
  199. open class DatePickerTableViewCell: UITableViewCell {
  200. let picker = UIDatePicker()
  201. let textField = UITextField()
  202. var onReload: (() -> Void)?
  203. init(share: TableShareable) {
  204. super.init(style: .value1, reuseIdentifier: "shareExpDate")
  205. picker.datePickerMode = .date
  206. picker.minimumDate = Date()
  207. if #available(iOS 13.4, *) {
  208. picker.preferredDatePickerStyle = .wheels
  209. }
  210. picker.action(for: .valueChanged) { datePicker in
  211. guard let datePicker = datePicker as? UIDatePicker else { return }
  212. self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date)
  213. }
  214. accessoryView = textField
  215. let toolbar = UIToolbar.toolbar {
  216. self.resignFirstResponder()
  217. share.expirationDate = nil
  218. self.onReload?()
  219. } completion: {
  220. self.resignFirstResponder()
  221. share.expirationDate = self.picker.date as NSDate
  222. self.onReload?()
  223. }
  224. textField.inputAccessoryView = toolbar
  225. textField.inputView = picker
  226. if let expDate = share.expirationDate {
  227. detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date)
  228. }
  229. }
  230. required public init?(coder: NSCoder) {
  231. fatalError("init(coder:) has not been implemented")
  232. }
  233. }