NCShareCells.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. //
  2. // NCShareCells.swift
  3. // Nextcloud
  4. //
  5. // Created by Henrik Storch on 18.03.22.
  6. // Copyright © 2022 Henrik Storch. All rights reserved.
  7. //
  8. // Author Henrik Storch <henrik.storch@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. protocol NCShareCellConfig {
  25. var title: String { get }
  26. func getCell(for share: NCTableShareable) -> UITableViewCell
  27. func didSelect(for share: NCTableShareable)
  28. }
  29. protocol NCToggleCellConfig: NCShareCellConfig {
  30. func isOn(for share: NCTableShareable) -> Bool
  31. func didChange(_ share: NCTableShareable, to newValue: Bool)
  32. }
  33. extension NCToggleCellConfig {
  34. func getCell(for share: NCTableShareable) -> UITableViewCell {
  35. return NCShareToggleCell(isOn: isOn(for: share))
  36. }
  37. func didSelect(for share: NCTableShareable) {
  38. didChange(share, to: !isOn(for: share))
  39. }
  40. }
  41. protocol NCPermission: NCToggleCellConfig {
  42. static var forDirectory: [Self] { get }
  43. static var forFile: [Self] { get }
  44. static func forDirectoryE2EE(account: String) -> [NCPermission]
  45. func hasResharePermission(for parentPermission: Int) -> Bool
  46. func hasDownload() -> Bool
  47. }
  48. enum NCUserPermission: CaseIterable, NCPermission {
  49. func hasResharePermission(for parentPermission: Int) -> Bool {
  50. if self == .download { return true }
  51. return ((permissionBitFlag & parentPermission) != 0)
  52. }
  53. func hasDownload() -> Bool {
  54. return self == .download
  55. }
  56. var permissionBitFlag: Int {
  57. switch self {
  58. case .reshare: return NCPermissions().permissionShareShare
  59. case .edit: return NCPermissions().permissionUpdateShare
  60. case .create: return NCPermissions().permissionCreateShare
  61. case .delete: return NCPermissions().permissionDeleteShare
  62. case .download: return NCPermissions().permissionDownloadShare
  63. }
  64. }
  65. func didChange(_ share: NCTableShareable, to newValue: Bool) {
  66. if self == .download {
  67. share.attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue)
  68. } else {
  69. share.permissions ^= permissionBitFlag
  70. }
  71. }
  72. func isOn(for share: NCTableShareable) -> Bool {
  73. if self == .download {
  74. return NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: share.attributes)
  75. } else {
  76. return (share.permissions & permissionBitFlag) != 0
  77. }
  78. }
  79. static func forDirectoryE2EE(account: String) -> [NCPermission] {
  80. if NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
  81. return NCUserPermission.allCases
  82. }
  83. return []
  84. }
  85. case reshare, edit, create, delete, download
  86. static let forDirectory: [NCUserPermission] = NCUserPermission.allCases
  87. static let forFile: [NCUserPermission] = [.reshare, .edit]
  88. var title: String {
  89. switch self {
  90. case .reshare: return NSLocalizedString("_share_can_reshare_", comment: "")
  91. case .edit: return NSLocalizedString("_share_can_change_", comment: "")
  92. case .create: return NSLocalizedString("_share_can_create_", comment: "")
  93. case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
  94. case .download: return NSLocalizedString("_share_can_download_", comment: "")
  95. }
  96. }
  97. }
  98. enum NCLinkPermission: NCPermission {
  99. func didChange(_ share: NCTableShareable, to newValue: Bool) {
  100. guard self != .allowEdit || newValue else {
  101. share.permissions = NCPermissions().permissionReadShare
  102. return
  103. }
  104. share.permissions = permissionValue
  105. }
  106. func hasResharePermission(for parentPermission: Int) -> Bool {
  107. permissionValue & parentPermission == permissionValue
  108. }
  109. func hasDownload() -> Bool {
  110. return false
  111. }
  112. var permissionValue: Int {
  113. switch self {
  114. case .allowEdit:
  115. return NCPermissions().getPermission(
  116. canEdit: true,
  117. canCreate: true,
  118. canChange: true,
  119. canDelete: true,
  120. canShare: false,
  121. isDirectory: false)
  122. case .viewOnly:
  123. return NCPermissions().getPermission(
  124. canEdit: false,
  125. canCreate: false,
  126. canChange: false,
  127. canDelete: false,
  128. // not possible to create "read-only" shares without reshare option
  129. // https://github.com/nextcloud/server/blame/f99876997a9119518fe5f7ad3a3a51d33459d4cc/apps/files_sharing/lib/Controller/ShareAPIController.php#L1104-L1107
  130. canShare: true,
  131. isDirectory: true)
  132. case .uploadEdit:
  133. return NCPermissions().getPermission(
  134. canEdit: true,
  135. canCreate: true,
  136. canChange: true,
  137. canDelete: true,
  138. canShare: false,
  139. isDirectory: true)
  140. case .fileDrop:
  141. return NCPermissions().permissionCreateShare
  142. case .secureFileDrop:
  143. return NCPermissions().permissionCreateShare
  144. }
  145. }
  146. func isOn(for share: NCTableShareable) -> Bool {
  147. let permissions = NCPermissions()
  148. switch self {
  149. case .allowEdit: return permissions.isAnyPermissionToEdit(share.permissions)
  150. case .viewOnly: return !permissions.isAnyPermissionToEdit(share.permissions) && share.permissions != permissions.permissionCreateShare
  151. case .uploadEdit: return permissions.isAnyPermissionToEdit(share.permissions) && share.permissions != permissions.permissionCreateShare
  152. case .fileDrop: return share.permissions == permissions.permissionCreateShare
  153. case .secureFileDrop: return share.permissions == permissions.permissionCreateShare
  154. }
  155. }
  156. static func forDirectoryE2EE(account: String) -> [NCPermission] {
  157. return [NCLinkPermission.secureFileDrop]
  158. }
  159. var title: String {
  160. switch self {
  161. case .allowEdit: return NSLocalizedString("_share_can_change_", comment: "")
  162. case .viewOnly: return NSLocalizedString("_share_read_only_", comment: "")
  163. case .uploadEdit: return NSLocalizedString("_share_allow_upload_", comment: "")
  164. case .fileDrop: return NSLocalizedString("_share_file_drop_", comment: "")
  165. case .secureFileDrop: return NSLocalizedString("_share_secure_file_drop_", comment: "")
  166. }
  167. }
  168. case allowEdit, viewOnly, uploadEdit, fileDrop, secureFileDrop
  169. static let forDirectory: [NCLinkPermission] = [.viewOnly, .uploadEdit, .fileDrop]
  170. static let forFile: [NCLinkPermission] = [.allowEdit]
  171. }
  172. enum NCShareDetails: CaseIterable, NCShareCellConfig {
  173. func didSelect(for share: NCTableShareable) {
  174. switch self {
  175. case .hideDownload: share.hideDownload.toggle()
  176. case .expirationDate: return
  177. case .password: return
  178. case .note: return
  179. case .label: return
  180. }
  181. }
  182. func getCell(for share: NCTableShareable) -> UITableViewCell {
  183. switch self {
  184. case .hideDownload:
  185. return NCShareToggleCell(isOn: share.hideDownload)
  186. case .expirationDate:
  187. return NCShareDateCell(share: share)
  188. case .password: return NCShareToggleCell(isOn: !share.password.isEmpty, customIcons: ("lock", "lock_open"))
  189. case .note:
  190. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareNote")
  191. cell.detailTextLabel?.text = share.note
  192. cell.accessoryType = .disclosureIndicator
  193. return cell
  194. case .label:
  195. let cell = UITableViewCell(style: .value1, reuseIdentifier: "shareLabel")
  196. cell.detailTextLabel?.text = share.label
  197. return cell
  198. }
  199. }
  200. var title: String {
  201. switch self {
  202. case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "")
  203. case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "")
  204. case .password: return NSLocalizedString("_share_password_protect_", comment: "")
  205. case .note: return NSLocalizedString("_share_note_recipient_", comment: "")
  206. case .label: return NSLocalizedString("_share_link_name_", comment: "")
  207. }
  208. }
  209. case label, hideDownload, expirationDate, password, note
  210. static let forLink: [NCShareDetails] = NCShareDetails.allCases
  211. static let forUser: [NCShareDetails] = [.expirationDate, .note]
  212. }
  213. struct NCShareConfig {
  214. let permissions: [NCPermission]
  215. let advanced: [NCShareDetails]
  216. let share: NCTableShareable
  217. let resharePermission: Int
  218. init(parentMetadata: tableMetadata, share: NCTableShareable) {
  219. self.share = share
  220. self.resharePermission = parentMetadata.sharePermissionsCollaborationServices
  221. let type: NCPermission.Type = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCLinkPermission.self : NCUserPermission.self
  222. self.permissions = parentMetadata.directory ? (parentMetadata.e2eEncrypted ? type.forDirectoryE2EE(account: parentMetadata.account) : type.forDirectory) : type.forFile
  223. self.advanced = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCShareDetails.forLink : NCShareDetails.forUser
  224. }
  225. func cellFor(indexPath: IndexPath) -> UITableViewCell? {
  226. let cellConfig = config(for: indexPath)
  227. let cell = cellConfig?.getCell(for: share)
  228. cell?.textLabel?.text = cellConfig?.title
  229. if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission), !cellConfig.hasDownload() {
  230. cell?.isUserInteractionEnabled = false
  231. cell?.textLabel?.isEnabled = false
  232. }
  233. return cell
  234. }
  235. func didSelectRow(at indexPath: IndexPath) {
  236. let cellConfig = config(for: indexPath)
  237. cellConfig?.didSelect(for: share)
  238. }
  239. func config(for indexPath: IndexPath) -> NCShareCellConfig? {
  240. if indexPath.section == 0, indexPath.row < permissions.count {
  241. return permissions[indexPath.row]
  242. } else if indexPath.section == 1, indexPath.row < advanced.count {
  243. return advanced[indexPath.row]
  244. } else { return nil }
  245. }
  246. }
  247. class NCShareToggleCell: UITableViewCell {
  248. typealias CustomToggleIcon = (onIconName: String?, offIconName: String?)
  249. init(isOn: Bool, customIcons: CustomToggleIcon? = nil) {
  250. super.init(style: .default, reuseIdentifier: "toggleCell")
  251. self.accessibilityValue = isOn ? NSLocalizedString("_on_", comment: "") : NSLocalizedString("_off_", comment: "")
  252. guard let customIcons = customIcons,
  253. let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else {
  254. self.accessoryType = isOn ? .checkmark : .none
  255. return
  256. }
  257. let image = NCUtility().loadImage(named: iconName, colors: [NCBrandColor.shared.customer], size: self.frame.height - 26)
  258. self.accessoryView = UIImageView(image: image)
  259. }
  260. required init?(coder: NSCoder) {
  261. fatalError("init(coder:) has not been implemented")
  262. }
  263. }
  264. class NCShareDateCell: UITableViewCell {
  265. let picker = UIDatePicker()
  266. let textField = UITextField()
  267. var shareType: Int
  268. var onReload: (() -> Void)?
  269. let shareCommon = NCShareCommon()
  270. init(share: NCTableShareable) {
  271. self.shareType = share.shareType
  272. super.init(style: .value1, reuseIdentifier: "shareExpDate")
  273. picker.datePickerMode = .date
  274. picker.minimumDate = Date()
  275. picker.preferredDatePickerStyle = .wheels
  276. picker.action(for: .valueChanged) { datePicker in
  277. guard let datePicker = datePicker as? UIDatePicker else { return }
  278. self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date)
  279. }
  280. accessoryView = textField
  281. let toolbar = UIToolbar.toolbar {
  282. self.resignFirstResponder()
  283. share.expirationDate = nil
  284. self.onReload?()
  285. } onDone: {
  286. self.resignFirstResponder()
  287. share.expirationDate = self.picker.date as NSDate
  288. self.onReload?()
  289. }
  290. textField.isAccessibilityElement = false
  291. textField.accessibilityElementsHidden = true
  292. textField.inputAccessoryView = toolbar.wrappedSafeAreaContainer
  293. textField.inputView = picker
  294. if let expDate = share.expirationDate {
  295. detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date)
  296. }
  297. }
  298. required public init?(coder: NSCoder) {
  299. fatalError("init(coder:) has not been implemented")
  300. }
  301. func checkMaximumDate(account: String) {
  302. let defaultExpDays = defaultExpirationDays(account: account)
  303. if defaultExpDays > 0 && isExpireDateEnforced(account: account) {
  304. let enforcedInSecs = TimeInterval(defaultExpDays * 24 * 60 * 60)
  305. self.picker.maximumDate = Date().advanced(by: enforcedInSecs)
  306. }
  307. }
  308. private func isExpireDateEnforced(account: String) -> Bool {
  309. switch self.shareType {
  310. case shareCommon.SHARE_TYPE_LINK,
  311. shareCommon.SHARE_TYPE_EMAIL,
  312. shareCommon.SHARE_TYPE_GUEST:
  313. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateEnforced
  314. case shareCommon.SHARE_TYPE_USER,
  315. shareCommon.SHARE_TYPE_GROUP,
  316. shareCommon.SHARE_TYPE_CIRCLE,
  317. shareCommon.SHARE_TYPE_ROOM:
  318. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateEnforced
  319. case shareCommon.SHARE_TYPE_REMOTE,
  320. shareCommon.SHARE_TYPE_REMOTE_GROUP:
  321. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateEnforced
  322. default:
  323. return false
  324. }
  325. }
  326. private func defaultExpirationDays(account: String) -> Int {
  327. switch self.shareType {
  328. case shareCommon.SHARE_TYPE_LINK,
  329. shareCommon.SHARE_TYPE_EMAIL,
  330. shareCommon.SHARE_TYPE_GUEST:
  331. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateDays
  332. case shareCommon.SHARE_TYPE_USER,
  333. shareCommon.SHARE_TYPE_GROUP,
  334. shareCommon.SHARE_TYPE_CIRCLE,
  335. shareCommon.SHARE_TYPE_ROOM:
  336. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateDays
  337. case shareCommon.SHARE_TYPE_REMOTE,
  338. shareCommon.SHARE_TYPE_REMOTE_GROUP:
  339. return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateDays
  340. default:
  341. return 0
  342. }
  343. }
  344. }