Skip to content

Commit

Permalink
Merge pull request #132 from boostcampwm-2024/feature/load-video-in-edit
Browse files Browse the repository at this point in the history
책 생성 & 페이지 볼 때 �비디오 저장 및 불러오기 구현
  • Loading branch information
Kyxxn authored Dec 3, 2024
2 parents 32c2eed + 596812a commit 7a4eefd
Show file tree
Hide file tree
Showing 19 changed files with 314 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extension MHFileManager: FileStorage {
else { return .failure(.directorySettingFailure) }

let newDataPath = newDirectory.appendingPathComponent(name)
MHLogger.debug("\(#function) \(newDataPath)")

do {
try fileManager.createDirectory(at: newDirectory, withIntermediateDirectories: true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import MHDomain
import MHCore
import AVFoundation

// TODO: nil이라면 바로 error를 return하도록 수정
public struct LocalMediaRepository: MediaRepository, Sendable {
private let storage: FileStorage
private let temporaryPath = "temp" // TODO: - 지워질 것임!
Expand All @@ -26,6 +25,7 @@ public struct LocalMediaRepository: MediaRepository, Sendable {

return await storage.create(at: path, fileName: fileName, data: data)
}

public func create(
media mediaDescription: MediaDescription,
from: URL,
Expand Down
12 changes: 2 additions & 10 deletions MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,10 @@ public struct DefaultFetchMediaUseCase: FetchMediaUseCase {

// MARK: - Method
public func execute(media: MediaDescription, in bookID: UUID) async throws -> Data {
do {
return try await repository.fetch(media: media, from: nil).get() // TODO: - 없어질 로직
} catch {
return try await repository.fetch(media: media, from: bookID).get()
}
try await repository.fetch(media: media, from: bookID).get()
}
public func execute(media: MediaDescription, in bookID: UUID) async throws -> URL {
do {
return try await repository.getURL(media: media, from: nil).get() // TODO: - 없어질 로직
} catch {
return try await repository.getURL(media: media, from: bookID).get()
}
try await repository.getURL(media: media, from: bookID).get()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ extension BookCoverViewController {
let customAlbumViewController = CustomAlbumViewController(
viewModel: albumViewModel,
mediaType: .image,
mode: .bookCover
mode: .bookCover,
videoSelectCompletionHandler: nil
) { imageData, _, _ in
self?.createInput.send(.changedBookImage(bookImage: imageData))
self?.modifyInput.send(.changedBookImage(bookImage: imageData))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,11 @@ final class BookCategoryViewController: UIViewController {
case .createdCategory, .updatedCategory, .fetchCategories, .deletedCategory:
self?.categoryTableView.reloadData()
case .failed(let errorMessage):
self?.handleError(with: errorMessage)
self?.showErrorAlert(with: errorMessage)
}
}.store(in: &cancellables)
}

private func handleError(with errorMessage: String) {
let alertController = UIAlertController(
title: "에러",
message: errorMessage,
preferredStyle: .alert
)
let okAction = UIAlertAction(title: "확인", style: .default)
alertController.addAction(okAction)

present(alertController, animated: true)
}

private func configureNavigationBar() {
navigationController?.navigationBar.isHidden = false
navigationController?.navigationBar.titleTextAttributes = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import MHCore
import UIKit
import Photos
import Combine
import PhotosUI

final class CustomAlbumViewController: UIViewController {
enum Mode {
case bookCover
case editPage
}

// MARK: - UI Components
private lazy var albumCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
Expand All @@ -28,19 +30,22 @@ final class CustomAlbumViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
private let mediaType: PHAssetMediaType
private let mode: Mode
private let completionHandler: (_ imageData: Data, _ creationDate: Date?, _ caption: String?) -> Void
private let videoSelectCompletionHandler: ((URL) -> Void)?
private let photoSelectCompletionHandler: ((Data, Date?, String?) -> Void)?

// MARK: - Initializer
init(
viewModel: CustomAlbumViewModel,
mediaType: PHAssetMediaType,
mode: Mode = .editPage,
completionHandler: @escaping (_ imageData: Data, _ creationDate: Date?, _ caption: String?) -> Void
videoSelectCompletionHandler: ((URL) -> Void)? = nil,
photoSelectCompletionHandler: ((Data, Date?, String?) -> Void)? = nil
) {
self.viewModel = viewModel
self.mediaType = mediaType
self.mode = mode
self.completionHandler = completionHandler
self.videoSelectCompletionHandler = videoSelectCompletionHandler
self.photoSelectCompletionHandler = photoSelectCompletionHandler

super.init(nibName: nil, bundle: nil)
}
Expand All @@ -49,7 +54,8 @@ final class CustomAlbumViewController: UIViewController {
self.viewModel = CustomAlbumViewModel()
self.mediaType = .image
self.mode = .bookCover
self.completionHandler = { _, _, _ in }
self.videoSelectCompletionHandler = { _ in }
self.photoSelectCompletionHandler = { _, _, _ in }

super.init(nibName: nil, bundle: nil)
}
Expand Down Expand Up @@ -163,7 +169,7 @@ final class CustomAlbumViewController: UIViewController {
// MARK: - Media
private func checkThumbnailAuthorization() {
let authorization = PHPhotoLibrary.authorizationStatus()

switch authorization {
case .notDetermined:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { @Sendable [weak self] status in
Expand Down Expand Up @@ -223,25 +229,34 @@ final class CustomAlbumViewController: UIViewController {
navigationController?.show(imagePicker, sender: nil)
}
}

private func moveToEditView(image: UIImage?, creationDate: Date) {
private func moveToEditPhotoView(image: UIImage?, creationDate: Date) {
guard let photoSelectCompletionHandler else { return }
var editPhotoViewController: EditPhotoViewController
switch mode {
case .bookCover:
editPhotoViewController = EditPhotoViewController(
mode: .bookCover,
completionHandler: completionHandler
completionHandler: photoSelectCompletionHandler
)
case .editPage:
editPhotoViewController = EditPhotoViewController(
mode: .editPage,
creationDate: creationDate,
completionHandler: completionHandler
completionHandler: photoSelectCompletionHandler
)
}
editPhotoViewController.setPhoto(image: image)
self.navigationController?.pushViewController(editPhotoViewController, animated: true)
}

private func moveToEditVideoView(url: URL) {
guard let videoSelectCompletionHandler else { return }
let editVideoViewController = EditVideoViewController(
videoURL: url,
videoSelectCompletionHandler: videoSelectCompletionHandler
)
self.navigationController?.pushViewController(editVideoViewController, animated: true)
}
}

// MARK: - UICollectionViewDelegate
Expand All @@ -254,15 +269,31 @@ extension CustomAlbumViewController: UICollectionViewDelegate {
self.checkCameraAuthorization()
} else {
guard let asset = viewModel.photoAsset?[indexPath.item - 1] else { return }
Task {
await LocalPhotoManager.shared.requestThumbnailImage(with: asset) { [weak self] image in
guard let self else { return }
if self.mediaType == .image {
moveToEditView(image: image, creationDate: asset.creationDate ?? .now)
} else {
// TODO: - 동영상 편집 뷰로 이동
}
}

if self.mediaType == .image {
handleImageSelection(with: asset)
} else {
handleVideoSelection(with: asset)
}
}
}

private func handleImageSelection(with asset: PHAsset) {
Task {
await LocalPhotoManager.shared.requestThumbnailImage(with: asset) { [weak self] image in
guard let self = self, let image = image else { return }
self.moveToEditPhotoView(image: image, creationDate: asset.creationDate ?? .now)
}
}
}

private func handleVideoSelection(with asset: PHAsset) {
Task {
if let videoURL = await LocalPhotoManager.shared.requestVideoURL(with: asset) {
MHLogger.info("\(#function) 비디오 URL: \(videoURL)")
self.moveToEditVideoView(url: videoURL)
} else {
self.showErrorAlert(with: "비디오 URL을 가져올 수 없습니다.")
}
}
}
Expand Down Expand Up @@ -310,7 +341,7 @@ extension CustomAlbumViewController: UIImagePickerControllerDelegate, UINavigati
) {
dismiss(animated: true)
let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
moveToEditView(image: image, creationDate: .now)
moveToEditPhotoView(image: image, creationDate: .now)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,20 @@ actor LocalPhotoManager {
Task { await completion(image) }
})
}

func requestVideoURL(
with asset: PHAsset
) async -> URL? {
await withCheckedContinuation { continuation in
let options = PHVideoRequestOptions()
options.version = .current
imageManager.requestAVAsset(forVideo: asset, options: options) { avAsset, _, _ in
if let urlAsset = avAsset as? AVURLAsset {
continuation.resume(returning: urlAsset.url)
} else {
continuation.resume(returning: nil)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import AVKit
import MHCore
import MHDomain

final class MHVideoView: UIView {
// MARK: - Property
let playerViewController = AVPlayerViewController()

// MARK: - Initializer
init() {
super.init(frame: .zero)
configureConstraint()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
configureConstraint()
}

// MARK: - Configuration
func configurePlayer(player: AVPlayer) {
playerViewController.player = player
playerViewController.showsPlaybackControls = true
}

private func configureConstraint() {
addSubview(playerViewController.view)
playerViewController.view.fillSuperview()
}
}

extension MHVideoView: @preconcurrency MediaAttachable {
func configureSource(with mediaDescription: MediaDescription, data: Data) {
let player = AVPlayer()
configurePlayer(player: player)
}

func configureSource(with mediaDescription: MediaDescription, url: URL) {
let player = AVPlayer(url: url)
configurePlayer(player: player)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ final class EditBookViewController: UIViewController {
return button
}()
private var buttonStackViewBottomConstraint: NSLayoutConstraint?

// MARK: - Property
private let viewModel: EditBookViewModel
private let input = PassthroughSubject<EditBookViewModel.Input, Never>()
Expand Down Expand Up @@ -212,13 +212,13 @@ final class EditBookViewController: UIViewController {
selector: #selector(keyboardWillAppear),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
)
// 스크롤이 될 때 키보드 내려가게 설정
editPageTableView.keyboardDismissMode = .onDrag
}
Expand All @@ -242,8 +242,8 @@ final class EditBookViewController: UIViewController {
}
case .revokeDone:
self?.navigationController?.popViewController(animated: true)
case .error(message: let message):
MHLogger.error(message) // TODO: - Alert 띄우기
case .error(let message):
self?.showErrorAlert(with: message)
}
}
.store(in: &cancellables)
Expand All @@ -254,7 +254,8 @@ final class EditBookViewController: UIViewController {
let customAlbumViewController = CustomAlbumViewController(
viewModel: albumViewModel,
mediaType: .image,
mode: .editPage
mode: .editPage,
videoSelectCompletionHandler: nil
) { imageData, creationDate, caption in
let attributes: [String: any Sendable] = [
Constant.photoCreationDate: creationDate?.toString(),
Expand All @@ -269,7 +270,18 @@ final class EditBookViewController: UIViewController {
addImageButton.addAction(addImageAction, for: .touchUpInside)

let addVideoAction = UIAction { [weak self] _ in
// TODO: - 비디오 추가 로직
let albumViewModel = CustomAlbumViewModel()
let customAlbumViewController = CustomAlbumViewController(
viewModel: albumViewModel,
mediaType: .video,
videoSelectCompletionHandler: { url in
self?.input.send(.didAddMediaInURL(type: .video, attributes: nil, url: url))
}
)

let navigationViewController = UINavigationController(rootViewController: customAlbumViewController)
navigationViewController.modalPresentationStyle = .fullScreen
self?.present(navigationViewController, animated: true)
}
addVideoButton.addAction(addVideoAction, for: .touchUpInside)

Expand Down
Loading

0 comments on commit 7a4eefd

Please sign in to comment.