Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

책 생성 & 페이지 볼 때 비디오 저장 및 불러오기 구현 #132

Merged
merged 41 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9ac67a6
chore: 불필요한 코드 제거
Kyxxn Dec 2, 2024
c7158c0
feat: showErrorAlert 추가
Kyxxn Dec 3, 2024
b2caff3
refactor: showErrorAlert로 로직 변경 및 중복된 코드 제거
Kyxxn Dec 3, 2024
dd2db49
feat: EditBook에서 비디오 추가 로직구현
Kyxxn Dec 3, 2024
49b1b1d
feat: LocalPhotoManager에서 비디오 영상 URL 가져오는 메소드 구현
Kyxxn Dec 3, 2024
11178b6
feat: AVKit을 이용해서 영상 보여주기
Kyxxn Dec 3, 2024
5c5d603
Merge branch 'develop' into feature/load-video-in-edit
Kyxxn Dec 3, 2024
28bdf86
fix: Photo 선택과 completion 겹치는 문제 해결
Kyxxn Dec 3, 2024
29dce63
fix: bookcover에서 dismiss, editPage에서 popVC로 변경
Kyxxn Dec 3, 2024
8b0d349
chore: 불필요한 코드 제거
Kyxxn Dec 3, 2024
180ddbb
fix: editBook에서 video 업로드 시, 다시 Editbook으로 이동
Kyxxn Dec 3, 2024
3ec4f56
feat: 동영상을 보여주는 MHVideoView 구현
Kyxxn Dec 3, 2024
fb7a52c
feat: AudioSession 조절하는 메소드 추가
Kyxxn Dec 3, 2024
dd6351a
feat: 편집화면에서 동영상 선택하는 UI 구현
Kyxxn Dec 3, 2024
b65bb44
Merge branch 'develop' into feature/load-video-in-edit
Kyxxn Dec 3, 2024
2524d43
refactor: 비디오 크기 조절
Kyxxn Dec 3, 2024
50f3694
fix: 머지 전 충돌해결
Kyxxn Dec 3, 2024
161913a
refactor: MainActor 제거
Kyxxn Dec 3, 2024
dd12c1c
feat: MHVideoView 로직 개선
Kyxxn Dec 3, 2024
b07d32d
fix: MHVideoView 로직 개선
Kyxxn Dec 3, 2024
b983842
fix: PageCell에서 뷰컨트롤러 의존성 제거
Kyxxn Dec 3, 2024
09527e3
refactor: MHVideoView 수정에 따른 EditVideoViewController 로직 변경
Kyxxn Dec 3, 2024
aac831b
feat: Read에서 비디오 불러오기
Kyxxn Dec 3, 2024
7e679d9
Merge branch 'develop' into feature/load-video-in-edit
Kyxxn Dec 3, 2024
7c5ebdd
fix: DefaultCreateMediaUseCase 에서 bookId를 안 주는 문제 해결
Kyxxn Dec 3, 2024
c9df84b
chore: 미디어 저장된 URL 받아오는 로그 설정
Kyxxn Dec 3, 2024
5198c53
refactor: 실패 시 Alert & fullScreen Present로 변경
Kyxxn Dec 3, 2024
368f7d7
refactor: ReadPageViewController 실패 예외처리
Kyxxn Dec 3, 2024
fb290eb
feat: 책 Read할 때 URL로부터 멀티미디어 받아오기
Kyxxn Dec 3, 2024
0878f8f
refactor: EditVideoViewController를 dismiss로 변경
Kyxxn Dec 3, 2024
deee9e4
refactor: EditPageCell에서 Video 불러오기
Kyxxn Dec 3, 2024
a26004d
fix: 커스텀 앨범에서 닫기 누른 경우 Dismiss 처리
Kyxxn Dec 3, 2024
8acf0c4
chore: 사진 편집에서 닫기 -> 취소 변경
Kyxxn Dec 3, 2024
4c8754e
chore: 가독성 증가
Kyxxn Dec 3, 2024
9caf51d
chore: 인덴트 수정
Kyxxn Dec 3, 2024
5741cd0
chore: 인덴트 수정
Kyxxn Dec 3, 2024
2e3113c
chore: 불필요한 코드 제거
Kyxxn Dec 3, 2024
fba0049
refactor: 비디오에서 Data 로직 구현
Kyxxn Dec 3, 2024
3bacbd3
chore: 불필요한 코드 제거
Kyxxn Dec 3, 2024
45424e9
fix: 동영상 업로드 화면에서 재생 중지 버튼 안 보이는 문제 해결
Kyxxn Dec 3, 2024
596812a
chore: 불필요한 코드 제거
Kyxxn Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,10 +4,9 @@
import MHCore
import AVFoundation

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

Check warning on line 9 in MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Todo Violation: TODOs should be resolved (- 지워질 것임!) (todo)

Check warning on line 9 in MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Todo Violation: TODOs should be resolved (- 지워질 것임!) (todo)
private let snapshotFileName = ".snapshot"

public init(storage: FileStorage) {
Expand All @@ -26,6 +25,7 @@

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

public func create(
media mediaDescription: MediaDescription,
from: URL,
Expand Down Expand Up @@ -107,7 +107,7 @@
let snapshotData = try await storage.read(at: path, fileName: snapshotFileName).get()
let mediaSet = Set<String>(try JSONDecoder().decode([String].self, from: snapshotData))
// snapshot 파일은 제외
let currentFiles = Set<String>(try await storage.getFileNames(at: path).get()).subtracting([snapshotFileName])

Check warning on line 110 in MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)

Check warning on line 110 in MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)
let shouldDelete = currentFiles.subtracting(mediaSet)
for fileName in shouldDelete {
_ = try await storage.delete(at: path, fileName: fileName).get()
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,45 @@
import AVKit
import MHCore
import MHDomain

final class MHVideoView: UIView {
// MARK: - Property
private let playerViewController: AVPlayerViewController

// MARK: - Initializer
init() {
self.playerViewController = AVPlayerViewController()

super.init(frame: .zero)
configureConstraint()
}

required init?(coder: NSCoder) {
self.playerViewController = AVPlayerViewController()

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) {
MHLogger.debug(#function)
}

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
Loading