MediaPreviewViewModel.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. //
  2. // MediaPreviewViewModel.swift
  3. // Mastodon
  4. //
  5. // Created by MainasuK Cirno on 2021-4-28.
  6. //
  7. import UIKit
  8. import Combine
  9. import CoreData
  10. import CoreDataStack
  11. import Pageboy
  12. import MastodonCore
  13. final class MediaPreviewViewModel: NSObject {
  14. weak var mediaPreviewImageViewControllerDelegate: MediaPreviewImageViewControllerDelegate?
  15. // input
  16. let context: AppContext
  17. let item: PreviewItem
  18. let transitionItem: MediaPreviewTransitionItem
  19. @Published var currentPage: Int
  20. // output
  21. let viewControllers: [UIViewController]
  22. init(
  23. context: AppContext,
  24. item: PreviewItem,
  25. transitionItem: MediaPreviewTransitionItem
  26. ) {
  27. self.context = context
  28. self.item = item
  29. var currentPage = 0
  30. var viewControllers: [UIViewController] = []
  31. switch item {
  32. case .attachment(let previewContext):
  33. currentPage = previewContext.initialIndex
  34. for (i, attachment) in previewContext.attachments.enumerated() {
  35. switch attachment.kind {
  36. case .image:
  37. let viewController = MediaPreviewImageViewController()
  38. let viewModel = MediaPreviewImageViewModel(
  39. context: context,
  40. item: .remote(.init(
  41. assetURL: attachment.assetURL.flatMap { URL(string: $0) },
  42. thumbnail: previewContext.thumbnail(at: i),
  43. altText: attachment.altDescription
  44. ))
  45. )
  46. viewController.viewModel = viewModel
  47. viewControllers.append(viewController)
  48. case .gifv:
  49. let viewController = MediaPreviewVideoViewController()
  50. let viewModel = MediaPreviewVideoViewModel(
  51. context: context,
  52. item: .gif(.init(
  53. assetURL: attachment.assetURL.flatMap { URL(string: $0) },
  54. previewURL: attachment.previewURL.flatMap { URL(string: $0) }
  55. ))
  56. )
  57. viewController.viewModel = viewModel
  58. viewControllers.append(viewController)
  59. case .video, .audio:
  60. let viewController = MediaPreviewVideoViewController()
  61. let viewModel = MediaPreviewVideoViewModel(
  62. context: context,
  63. item: .video(.init(
  64. assetURL: attachment.assetURL.flatMap { URL(string: $0) },
  65. previewURL: attachment.previewURL.flatMap { URL(string: $0) }
  66. ))
  67. )
  68. viewController.viewModel = viewModel
  69. viewControllers.append(viewController)
  70. } // end switch attachment.kind { … }
  71. } // end for … in …
  72. case .profileAvatar(let previewContext):
  73. let viewController = MediaPreviewImageViewController()
  74. let viewModel = MediaPreviewImageViewModel(
  75. context: context,
  76. item: .remote(.init(
  77. assetURL: previewContext.assetURL.flatMap { URL(string: $0) },
  78. thumbnail: previewContext.thumbnail,
  79. altText: nil
  80. ))
  81. )
  82. viewController.viewModel = viewModel
  83. viewControllers.append(viewController)
  84. case .profileBanner(let previewContext):
  85. let viewController = MediaPreviewImageViewController()
  86. let viewModel = MediaPreviewImageViewModel(
  87. context: context,
  88. item: .remote(.init(
  89. assetURL: previewContext.assetURL.flatMap { URL(string: $0) },
  90. thumbnail: previewContext.thumbnail,
  91. altText: nil
  92. ))
  93. )
  94. viewController.viewModel = viewModel
  95. viewControllers.append(viewController)
  96. } // end switch
  97. self.viewControllers = viewControllers
  98. self.currentPage = currentPage
  99. self.transitionItem = transitionItem
  100. super.init()
  101. }
  102. }
  103. extension MediaPreviewViewModel {
  104. enum PreviewItem {
  105. case attachment(AttachmentPreviewContext)
  106. case profileAvatar(ProfileAvatarPreviewContext)
  107. case profileBanner(ProfileBannerPreviewContext)
  108. // case local(LocalImagePreviewMeta)
  109. var isAssetURLValid: Bool {
  110. switch self {
  111. case .attachment:
  112. return true // default valid
  113. case .profileAvatar:
  114. return true // default valid
  115. case .profileBanner(let item):
  116. guard let assertURL = item.assetURL else { return false }
  117. guard !assertURL.hasSuffix("missing.png") else { return false }
  118. return true
  119. }
  120. }
  121. }
  122. struct AttachmentPreviewContext {
  123. let attachments: [MastodonAttachment]
  124. let initialIndex: Int
  125. let thumbnails: [UIImage?]
  126. func thumbnail(at index: Int) -> UIImage? {
  127. guard index < thumbnails.count else { return nil }
  128. return thumbnails[index]
  129. }
  130. }
  131. struct ProfileAvatarPreviewContext {
  132. let assetURL: String?
  133. let thumbnail: UIImage?
  134. }
  135. struct ProfileBannerPreviewContext {
  136. let assetURL: String?
  137. let thumbnail: UIImage?
  138. }
  139. // struct LocalImagePreviewMeta {
  140. // let image: UIImage
  141. // }
  142. }
  143. // MARK: - PageboyViewControllerDataSource
  144. extension MediaPreviewViewModel: PageboyViewControllerDataSource {
  145. func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
  146. return viewControllers.count
  147. }
  148. func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
  149. let viewController = viewControllers[index]
  150. if let mediaPreviewImageViewController = viewController as? MediaPreviewImageViewController {
  151. mediaPreviewImageViewController.delegate = mediaPreviewImageViewControllerDelegate
  152. }
  153. return viewController
  154. }
  155. func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
  156. guard case let .attachment(previewContext) = item else { return nil }
  157. return .at(index: previewContext.initialIndex)
  158. }
  159. }