NCMenu.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. //
  2. // NCMainMenuTableViewController.swift
  3. // Nextcloud
  4. //
  5. // Created by Philippe Weidmann on 16.01.20.
  6. // Copyright © 2020 Philippe Weidmann. All rights reserved.
  7. // Copyright © 2020 Marino Faggiana All rights reserved.
  8. // Copyright © 2021 Henrik Storch All rights reserved.
  9. //
  10. // Author Philippe Weidmann <philippe.weidmann@infomaniak.com>
  11. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  12. // Author Henrik Storch <henrik.storch@nextcloud.com>
  13. //
  14. // This program is free software: you can redistribute it and/or modify
  15. // it under the terms of the GNU General Public License as published by
  16. // the Free Software Foundation, either version 3 of the License, or
  17. // (at your option) any later version.
  18. //
  19. // This program is distributed in the hope that it will be useful,
  20. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. // GNU General Public License for more details.
  23. //
  24. // You should have received a copy of the GNU General Public License
  25. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. //
  27. import UIKit
  28. import FloatingPanel
  29. extension Array where Element == NCMenuAction {
  30. var listHeight: CGFloat { reduce(0, { $0 + $1.rowHeight }) }
  31. }
  32. class NCMenu: UITableViewController {
  33. var actions: [NCMenuAction]
  34. var headerActions: [NCMenuAction]?
  35. var actionsHeight: CGFloat {
  36. let listHeight = (actions + (headerActions ?? [])).listHeight
  37. guard #available(iOS 13, *) else { return listHeight }
  38. return listHeight + 30
  39. }
  40. init(actions: [NCMenuAction]) {
  41. self.actions = actions
  42. super.init(nibName: nil, bundle: nil)
  43. let splitActions = actions.split(whereSeparator: { $0.title == NCMenuAction.seperatorIdentifier })
  44. guard splitActions.count == 2, #available(iOS 13, *) else { return }
  45. self.actions = Array(splitActions[1])
  46. self.headerActions = Array(splitActions[0])
  47. }
  48. required init?(coder: NSCoder) {
  49. fatalError("init(coder:) has not been implemented")
  50. }
  51. // MARK: - View Life Cycle
  52. override func viewDidLoad() {
  53. super.viewDidLoad()
  54. tableView.register(UINib(nibName: "NCMenuCell", bundle: nil), forCellReuseIdentifier: "menuActionCell")
  55. tableView.estimatedRowHeight = 60
  56. tableView.rowHeight = UITableView.automaticDimension
  57. tableView.dataSource = self
  58. tableView.delegate = self
  59. tableView.alwaysBounceVertical = false
  60. }
  61. func setupCell(_ cell: UIView, with action: NCMenuAction) {
  62. let actionIconView = cell.viewWithTag(1) as? UIImageView
  63. let actionNameLabel = cell.viewWithTag(2) as? UILabel
  64. let actionDetailLabel = cell.viewWithTag(3) as? UILabel
  65. cell.tintColor = NCBrandColor.shared.customer
  66. if let details = action.details {
  67. actionDetailLabel?.text = details
  68. actionNameLabel?.isHidden = false
  69. } else { actionDetailLabel?.isHidden = true }
  70. if action.isOn {
  71. actionIconView?.image = action.onIcon
  72. actionNameLabel?.text = action.onTitle
  73. } else {
  74. actionIconView?.image = action.icon
  75. actionNameLabel?.text = action.title
  76. }
  77. }
  78. override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  79. return headerActions?.listHeight ?? 0
  80. }
  81. override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  82. guard let headerActions = headerActions else { return nil }
  83. let headerView = UIStackView()
  84. headerView.distribution = .fillProportionally
  85. headerView.axis = .vertical
  86. headerView.backgroundColor = tableView.backgroundColor
  87. for action in headerActions {
  88. guard let cell = Bundle.main.loadNibNamed("NCMenuCell", owner: self, options: nil)?[0] as? UIView else { continue }
  89. setupCell(cell, with: action)
  90. cell.backgroundColor = tableView.backgroundColor
  91. headerView.addArrangedSubview(cell)
  92. let separator = UIView()
  93. separator.heightAnchor.constraint(equalToConstant: NCMenuAction.seperatorHeight).isActive = true
  94. separator.backgroundColor = NCBrandColor.shared.separator
  95. headerView.addArrangedSubview(separator)
  96. }
  97. return headerView
  98. }
  99. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  100. let menuAction = actions[indexPath.row]
  101. if let action = menuAction.action {
  102. self.dismiss(animated: true, completion: nil)
  103. action(menuAction)
  104. }
  105. }
  106. // MARK: - Table view data source
  107. override func numberOfSections(in tableView: UITableView) -> Int {
  108. return 1
  109. }
  110. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  111. return actions.count
  112. }
  113. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  114. let action = actions[indexPath.row]
  115. guard action.title != NCMenuAction.seperatorIdentifier else {
  116. let cell = UITableViewCell()
  117. cell.backgroundColor = NCBrandColor.shared.separator
  118. return cell
  119. }
  120. let cell = tableView.dequeueReusableCell(withIdentifier: "menuActionCell", for: indexPath)
  121. if action.action == nil {
  122. cell.selectionStyle = .none
  123. }
  124. setupCell(cell, with: action)
  125. cell.accessoryType = action.selectable && action.selected ? .checkmark : .none
  126. return cell
  127. }
  128. // MARK: - Tabel View Layout
  129. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  130. actions[indexPath.row].title == NCMenuAction.seperatorIdentifier ? NCMenuAction.seperatorHeight : UITableView.automaticDimension
  131. }
  132. }
  133. extension NCMenu: FloatingPanelControllerDelegate {
  134. func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout {
  135. return NCMenuFloatingPanelLayout(actionsHeight: self.actionsHeight)
  136. }
  137. func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
  138. return NCMenuFloatingPanelLayout(actionsHeight: self.actionsHeight)
  139. }
  140. func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator {
  141. return UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut)
  142. }
  143. func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator {
  144. return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
  145. }
  146. func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
  147. guard velocity.y > 750 else { return }
  148. fpc.dismiss(animated: true, completion: nil)
  149. }
  150. }