PPOptionsViewController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. // This file is based on third party code, see below for the original author
  2. // and original license.
  3. // Modifications are (c) by Threema GmbH and licensed under the AGPLv3.
  4. // Copyright (c) 2016 Pavel Pantus <pantusp@gmail.com>
  5. // See Resources/License.html for original license
  6. import UIKit
  7. protocol PPOptionsViewControllerDelegate: class {
  8. func optionsViewControllerShouldBeDismissed(_ controller: PPOptionsViewController)
  9. func optionsViewControllerDidRequestTopOption(_ controller: PPOptionsViewController)
  10. func optionsViewControllerDidRequestOwnOption(_ controller: PPOptionsViewController)
  11. func optionsViewControllerDidRequestPreviewReplacementOption(_ controller: PPOptionsViewController)
  12. }
  13. /**
  14. Bottom part of Assets Picker Controller that consists of provided options,
  15. Cancel and Snap Photo or Video / Send X Items buttons.
  16. */
  17. class PPOptionsViewController: UITableViewController {
  18. public weak var delegate: PPOptionsViewControllerDelegate?
  19. private var tableHeightConstraint: NSLayoutConstraint!
  20. private var selctionCount: Int = 0
  21. public var options: [PPOption] = []
  22. fileprivate var config: PPAssetsActionConfig!
  23. private var cellWidth: CGFloat?
  24. private var snapOption: PPOption?
  25. private var onlyPhotosSelected:Bool! = false
  26. private var onlyVideosSelected:Bool! = false
  27. private let assetManager = PPAssetManager()
  28. init(aConfig: PPAssetsActionConfig) {
  29. super.init(style: .plain)
  30. config = aConfig
  31. }
  32. override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
  33. super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  34. }
  35. required init?(coder aDecoder: NSCoder) {
  36. super.init(coder: aDecoder)
  37. }
  38. override func viewDidLoad() {
  39. super.viewDidLoad()
  40. snapOption = PPOption.init(withTitle: config.previewReplacementText, withIcon: config.previewReplacementIcon, handler: {
  41. self.delegate?.optionsViewControllerDidRequestPreviewReplacementOption(self)
  42. })
  43. tableView.register(UITableViewCell.self, forCellReuseIdentifier: "option_cell_id")
  44. tableView.translatesAutoresizingMaskIntoConstraints = false
  45. tableView.backgroundColor = UIColor.clear
  46. tableView.bounces = false
  47. tableView.estimatedRowHeight = 0
  48. tableView.estimatedSectionFooterHeight = 0
  49. tableView.estimatedSectionHeaderHeight = 0
  50. tableView.separatorInset = UIEdgeInsets.zero
  51. tableView.separatorInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 8.0)
  52. tableHeightConstraint = NSLayoutConstraint(item: tableView,
  53. attribute: .height,
  54. relatedBy: .equal,
  55. toItem: nil,
  56. attribute: .notAnAttribute,
  57. multiplier: 1.0,
  58. constant: 0.0)
  59. tableView.addConstraint(tableHeightConstraint)
  60. /***** BEGIN THREEMA MODIFICATION: separatorColor *********/
  61. tableView.separatorColor = Colors.hairline()
  62. /***** END THREEMA MODIFICATION: separatorColor *********/
  63. }
  64. override func updateViewConstraints() {
  65. super.updateViewConstraints()
  66. tableHeightConstraint.constant = tableView.contentSize.height
  67. }
  68. public func refresh() {
  69. tableView.reloadData()
  70. updateViewConstraints()
  71. }
  72. override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  73. super.viewWillTransition(to: size, with: coordinator)
  74. cellWidth = size.width - (2 * config.inset)
  75. tableView.reloadData()
  76. updateViewConstraints()
  77. }
  78. func set(sendItemsCount count: Int, _ onlyPhotos: Bool, _ onlyVideos: Bool) {
  79. selctionCount = count
  80. var rowsToUpdate:Array<IndexPath> = Array()
  81. for i in 1 ..< options.count+1 {
  82. rowsToUpdate.append(IndexPath(row: i, section: 0))
  83. }
  84. onlyPhotosSelected = onlyPhotos
  85. onlyVideosSelected = onlyVideos
  86. tableView.reloadData()
  87. updateViewConstraints()
  88. }
  89. // MARK: - Table view data source
  90. override func numberOfSections(in tableView: UITableView) -> Int {
  91. return 2
  92. }
  93. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  94. let isIpad = UIDevice.current.userInterfaceIdiom == .pad
  95. switch section {
  96. case 0:
  97. if selctionCount > 0 && (!config.isLandscape() || (config.isLandscape() && isIpad)) && assetManager.authorizationStatus() == .authorized && config.showGalleryPreview {
  98. if config.showAdditionalOptionWhenAssetIsSelected {
  99. return 2
  100. } else {
  101. return 1
  102. }
  103. } else {
  104. var count = options.count + 1;
  105. if config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable() {
  106. count += 1
  107. }
  108. return count
  109. }
  110. case 1:
  111. return 1
  112. default:
  113. return 0
  114. }
  115. }
  116. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  117. let cell = tableView.dequeueReusableCell(withIdentifier: "option_cell_id", for: indexPath)
  118. cell.textLabel?.textColor = config.tintColor
  119. cell.textLabel?.font = config.font
  120. cell.imageView?.image = nil
  121. cell.contentView.alignmentRect(forFrame: CGRect(x: 100, y: 0, width: 0, height: 0))
  122. if indexPath.section == 0 {
  123. cell.textLabel?.textAlignment = config.textAlignment
  124. var specifiedRow = 0
  125. if config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable() {
  126. specifiedRow = 1
  127. }
  128. if indexPath.row == specifiedRow && (!config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable()) {
  129. if selctionCount > 0 && !config.showOptionsWhenAssetIsSelected {
  130. if selctionCount == 1 {
  131. if (onlyPhotosSelected) {
  132. cell.textLabel?.text = NSLocalizedString("send_item_photo", comment: "")
  133. } else {
  134. cell.textLabel?.text = NSLocalizedString("send_item_video", comment: "")
  135. }
  136. } else {
  137. if (onlyPhotosSelected) {
  138. cell.textLabel?.text = String.localizedStringWithFormat(NSLocalizedString("send_items_photo", comment: ""), selctionCount)
  139. } else if (onlyVideosSelected) {
  140. cell.textLabel?.text = String.localizedStringWithFormat(NSLocalizedString("send_items_video", comment: ""), selctionCount)
  141. } else {
  142. cell.textLabel?.text = String.localizedStringWithFormat(NSLocalizedString("send_items", comment: ""), selctionCount)
  143. }
  144. }
  145. } else {
  146. if config.useOwnSnapButton && config.ownSnapButtonText != nil {
  147. cell.textLabel?.text = config.ownSnapButtonText
  148. if config.ownSnapButtonIcon != nil {
  149. cell.imageView?.image = config.ownSnapButtonIcon
  150. }
  151. } else {
  152. cell.textLabel?.text = NSLocalizedString("Snap Photo or Video", comment: "Snap Photo or Video")
  153. }
  154. }
  155. } else {
  156. if indexPath.row == 0 && (config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable()) {
  157. cell.textLabel?.text = config.previewReplacementText
  158. if config.previewReplacementIcon != nil {
  159. cell.imageView?.image = config.previewReplacementIcon
  160. }
  161. } else if indexPath.row == 1 && (config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable()) {
  162. if config.useOwnSnapButton && config.ownSnapButtonText != nil {
  163. cell.textLabel?.text = config.ownSnapButtonText
  164. if config.ownSnapButtonIcon != nil {
  165. cell.imageView?.image = config.ownSnapButtonIcon
  166. }
  167. } else {
  168. cell.textLabel?.text = NSLocalizedString("Snap Photo or Video", comment: "Snap Photo or Video")
  169. }
  170. }
  171. else if indexPath.row == specifiedRow + 1 && selctionCount > 0 && !config.showOptionsWhenAssetIsSelected && config.showAdditionalOptionWhenAssetIsSelected && config.additionalOptionText != nil {
  172. cell.textLabel?.text = config.additionalOptionText
  173. } else {
  174. let row = config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable() ? indexPath.row - 2 : indexPath.row - 1
  175. cell.textLabel?.text = options[row].title
  176. cell.imageView?.image = options[row].icon
  177. }
  178. }
  179. var optionsCount = options.count
  180. let isIpad = UIDevice.current.userInterfaceIdiom == .pad
  181. if selctionCount > 0 && !config.showOptionsWhenAssetIsSelected && (!config.isLandscape() || (config.isLandscape() && isIpad)) && assetManager.authorizationStatus() == .authorized && config.showGalleryPreview{
  182. optionsCount = 1
  183. } else {
  184. if config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable() {
  185. optionsCount += 1
  186. }
  187. }
  188. if indexPath.row == 0 && ((config.isLandscape() && !isIpad) || UIAccessibility.isVoiceOverRunning || assetManager.authorizationStatus() != .authorized || !config.showGalleryPreview) {
  189. if cellWidth == nil {
  190. cellWidth = cell.bounds.size.width
  191. }
  192. let cellBounds = CGRect(x: cell.bounds.origin.x, y: cell.bounds.origin.y, width: cellWidth!, height: cell.bounds.size.height)
  193. let maskPath = UIBezierPath(roundedRect: cellBounds,
  194. byRoundingCorners: [.topRight, .topLeft],
  195. cornerRadii: CGSize(width: config.cornerRadius, height: config.cornerRadius))
  196. let maskLayer = CAShapeLayer()
  197. maskLayer.frame = cellBounds
  198. maskLayer.path = maskPath.cgPath
  199. cell.layer.mask = maskLayer
  200. cell.layer.cornerRadius = 0
  201. } else if ((indexPath.row == optionsCount) || (selctionCount > 0 && ((config.showAdditionalOptionWhenAssetIsSelected && indexPath.row == 1) || (!config.showAdditionalOptionWhenAssetIsSelected && indexPath.row == 0)))) {
  202. if cellWidth == nil {
  203. cellWidth = cell.bounds.size.width
  204. }
  205. let cellBounds = CGRect(x: cell.bounds.origin.x, y: cell.bounds.origin.y, width: cellWidth!, height: cell.bounds.size.height)
  206. let maskPath = UIBezierPath(roundedRect: cellBounds,
  207. byRoundingCorners: [.bottomRight, .bottomLeft],
  208. cornerRadii: CGSize(width: config.cornerRadius, height: config.cornerRadius))
  209. let maskLayer = CAShapeLayer()
  210. maskLayer.frame = cellBounds
  211. maskLayer.path = maskPath.cgPath
  212. cell.layer.mask = maskLayer
  213. cell.layer.cornerRadius = 0
  214. } else {
  215. cell.layer.cornerRadius = 0
  216. cell.layer.mask = nil
  217. cell.layer.masksToBounds = false
  218. }
  219. } else if (indexPath.section == 1) {
  220. cell.textLabel?.textAlignment = .center
  221. cell.textLabel?.text = NSLocalizedString("cancel", comment: "")
  222. cell.accessibilityLabel = NSLocalizedString("cancel", comment: "")
  223. cell.accessibilityIdentifier = "Cancel"
  224. /***** BEGIN THREEMA MODIFICATION: Use bold font for cancel *********/
  225. cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 19.0)
  226. /***** END THREEMA MODIFICATION: Use bold font for cancel *********/
  227. cell.layer.cornerRadius = config.cornerRadius
  228. cell.layer.masksToBounds = true
  229. /***** BEGIN THREEMA MODIFICATION: Hide separator on cancel cell *********/
  230. cell.separatorInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude)
  231. /***** END THREEMA MODIFICATION: Hide separator on cancel cell *********/
  232. }
  233. return cell
  234. }
  235. override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  236. switch section {
  237. case 1:
  238. return config.sectionSpacing
  239. default:
  240. return 0.0
  241. }
  242. }
  243. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  244. return config.buttonHeight
  245. }
  246. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  247. tableView.deselectRow(at: indexPath, animated: true)
  248. if indexPath.section == 1 {
  249. delegate?.optionsViewControllerShouldBeDismissed(self)
  250. } else {
  251. if indexPath.row == 0 && (config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable()) {
  252. delegate?.optionsViewControllerDidRequestPreviewReplacementOption(self)
  253. } else if indexPath.row == 0 {
  254. delegate?.optionsViewControllerDidRequestTopOption(self)
  255. } else if indexPath.row == 1 && (config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable()) {
  256. delegate?.optionsViewControllerDidRequestTopOption(self)
  257. }
  258. else if indexPath.row > 0 {
  259. if selctionCount > 0 && config.showAdditionalOptionWhenAssetIsSelected && config.additionalOptionText != nil {
  260. delegate?.optionsViewControllerDidRequestOwnOption(self)
  261. } else {
  262. if config.showReplacementOptionInLandscape() || assetManager.isUnauthorizedAndCameraAvailable() {
  263. options[indexPath.row - 2].handler()
  264. } else {
  265. options[indexPath.row - 1].handler()
  266. }
  267. }
  268. }
  269. }
  270. }
  271. /***** BEGIN THREEMA MODIFICATION: cell color *********/
  272. override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  273. if cell.isKind(of: UITableViewCell.self) {
  274. Colors.updateTableViewCellBackground(cell)
  275. }
  276. }
  277. /***** END THREEMA MODIFICATION: cell color *********/
  278. override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
  279. if let headerView = view as? UITableViewHeaderFooterView {
  280. /***** BEGIN THREEMA MODIFICATION: clear color for header view *********/
  281. headerView.backgroundView = nil
  282. headerView.backgroundColor = .clear
  283. /***** END THREEMA MODIFICATION: clear color for header view *********/
  284. }
  285. }
  286. /***** BEGIN THREEMA MODIFICATION: iOS 14 fix: clear color for header view *********/
  287. override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  288. let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: tableView.frame.size.width, height: config.sectionSpacing))
  289. view.backgroundColor = .clear
  290. return view
  291. }
  292. /***** END THREEMA MODIFICATION: iOS 14 fix: clear color for header view *********/
  293. }