GalleryViewController.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import UIKit
  2. import DcCore
  3. class GalleryViewController: UIViewController {
  4. // MARK: - data
  5. private let mediaMessageIds: [Int]
  6. // MARK: - subview specs
  7. private let gridDefaultSpacing: CGFloat = 5
  8. private lazy var gridLayout: GridCollectionViewFlowLayout = {
  9. let layout = GridCollectionViewFlowLayout()
  10. layout.minimumLineSpacing = gridDefaultSpacing
  11. layout.minimumInteritemSpacing = gridDefaultSpacing
  12. layout.format = .square
  13. return layout
  14. }()
  15. private lazy var grid: UICollectionView = {
  16. let collection = UICollectionView(frame: .zero, collectionViewLayout: gridLayout)
  17. collection.dataSource = self
  18. collection.delegate = self
  19. collection.register(GalleryCell.self, forCellWithReuseIdentifier: GalleryCell.reuseIdentifier)
  20. collection.contentInset = UIEdgeInsets(top: gridDefaultSpacing, left: gridDefaultSpacing, bottom: gridDefaultSpacing, right: gridDefaultSpacing)
  21. collection.backgroundColor = .white
  22. collection.delaysContentTouches = false
  23. collection.alwaysBounceVertical = true
  24. return collection
  25. }()
  26. private lazy var timeLabel: GalleryTimeLabel = {
  27. let view = GalleryTimeLabel()
  28. view.hide(animated: false)
  29. return view
  30. }()
  31. private lazy var emptyStateView: EmptyStateLabel = {
  32. let label = EmptyStateLabel()
  33. label.text = String.localized("chat_gallery_empty_state")
  34. label.isHidden = true
  35. return label
  36. }()
  37. init(mediaMessageIds: [Int]) {
  38. self.mediaMessageIds = mediaMessageIds.reversed()
  39. super.init(nibName: nil, bundle: nil)
  40. }
  41. required init?(coder: NSCoder) {
  42. fatalError("init(coder:) has not been implemented")
  43. }
  44. // MARK: - lifecycle
  45. override func viewDidLoad() {
  46. super.viewDidLoad()
  47. setupSubviews()
  48. title = String.localized("gallery")
  49. if mediaMessageIds.isEmpty {
  50. emptyStateView.isHidden = false
  51. }
  52. }
  53. override func viewWillAppear(_ animated: Bool) {
  54. grid.reloadData()
  55. }
  56. override func viewWillLayoutSubviews() {
  57. super.viewWillLayoutSubviews()
  58. self.reloadCollectionViewLayout()
  59. }
  60. // MARK: - setup
  61. private func setupSubviews() {
  62. view.addSubview(grid)
  63. grid.translatesAutoresizingMaskIntoConstraints = false
  64. grid.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
  65. grid.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
  66. grid.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
  67. grid.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
  68. view.addSubview(timeLabel)
  69. timeLabel.translatesAutoresizingMaskIntoConstraints = false
  70. timeLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
  71. timeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
  72. view.addSubview(emptyStateView)
  73. emptyStateView.translatesAutoresizingMaskIntoConstraints = false
  74. emptyStateView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
  75. emptyStateView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor).isActive = true
  76. emptyStateView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor).isActive = true
  77. emptyStateView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
  78. }
  79. // MARK: - updates
  80. private func updateFloatingTimeLabel() {
  81. if let indexPath = grid.indexPathsForVisibleItems.min() {
  82. let msgId = mediaMessageIds[indexPath.row]
  83. let msg = DcMsg(id: msgId)
  84. timeLabel.update(date: msg.sentDate)
  85. }
  86. }
  87. }
  88. // MARK: - UICollectionViewDataSource, UICollectionViewDelegate
  89. extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
  90. func numberOfSections(in collectionView: UICollectionView) -> Int {
  91. return 1
  92. }
  93. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  94. return mediaMessageIds.count
  95. }
  96. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  97. guard let galleryCell = collectionView.dequeueReusableCell(
  98. withReuseIdentifier: GalleryCell.reuseIdentifier,
  99. for: indexPath) as? GalleryCell else {
  100. return UICollectionViewCell()
  101. }
  102. let msgId = mediaMessageIds[indexPath.row]
  103. let msg = DcMsg(id: msgId)
  104. galleryCell.update(msg: msg)
  105. return galleryCell
  106. }
  107. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  108. let msgId = mediaMessageIds[indexPath.row]
  109. showPreview(msgId: msgId)
  110. }
  111. func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  112. updateFloatingTimeLabel()
  113. timeLabel.show(animated: true)
  114. }
  115. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  116. updateFloatingTimeLabel()
  117. }
  118. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  119. timeLabel.hide(animated: true)
  120. }
  121. }
  122. // MARK: - grid layout + updates
  123. private extension GalleryViewController {
  124. func reloadCollectionViewLayout() {
  125. // columns specification
  126. let phonePortrait = 3
  127. let phoneLandscape = 4
  128. let padPortrait = 5
  129. let padLandscape = 8
  130. let orientation = UIApplication.shared.statusBarOrientation
  131. let deviceType = UIDevice.current.userInterfaceIdiom
  132. var gridDisplay: GridDisplay?
  133. if deviceType == .phone {
  134. if orientation.isPortrait {
  135. gridDisplay = .grid(columns: phonePortrait)
  136. } else {
  137. gridDisplay = .grid(columns: phoneLandscape)
  138. }
  139. } else if deviceType == .pad {
  140. if orientation.isPortrait {
  141. gridDisplay = .grid(columns: padPortrait)
  142. } else {
  143. gridDisplay = .grid(columns: padLandscape)
  144. }
  145. }
  146. if let gridDisplay = gridDisplay {
  147. gridLayout.display = gridDisplay
  148. } else {
  149. safe_fatalError("undefined format")
  150. }
  151. let containerWidth = view.bounds.width - view.safeAreaInsets.left - view.safeAreaInsets.right - 2 * gridDefaultSpacing
  152. gridLayout.containerWidth = containerWidth
  153. }
  154. }
  155. // MARK: - coordinator
  156. extension GalleryViewController {
  157. func showPreview(msgId: Int) {
  158. guard let index = mediaMessageIds.index(of: msgId) else {
  159. return
  160. }
  161. let mediaUrls = mediaMessageIds.compactMap {
  162. return DcMsg(id: $0).fileURL
  163. }
  164. let previewController = PreviewController(currentIndex: index, urls: mediaUrls)
  165. present(previewController, animated: true, completion: nil)
  166. }
  167. }