From 0d5bbd10efebbc7a28d97f0905b5934c05af2d0a Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Mon, 2 Dec 2024 13:00:47 +0900 Subject: [PATCH 01/35] WIP on feature/audioplayer --- .../View}/AudioViewController.swift | 0 .../ViewModel}/CreateAudioViewModel.swift | 0 .../Audio/Player/Enum/AudioPlayState.swift | 4 + .../Audio/Player/View/AudioPlayerView.swift | 131 ++++++++++++++++++ .../ViewModel/AudioPlayerViewModel.swift | 38 +++++ 5 files changed, 173 insertions(+) rename MemorialHouse/MHPresentation/MHPresentation/Source/Audio/{ => Audio/View}/AudioViewController.swift (100%) rename MemorialHouse/MHPresentation/MHPresentation/Source/Audio/{ => Audio/ViewModel}/CreateAudioViewModel.swift (100%) create mode 100644 MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/Enum/AudioPlayState.swift create mode 100644 MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift create mode 100644 MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/ViewModel/AudioPlayerViewModel.swift diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/AudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift similarity index 100% rename from MemorialHouse/MHPresentation/MHPresentation/Source/Audio/AudioViewController.swift rename to MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift similarity index 100% rename from MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewModel.swift rename to MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/Enum/AudioPlayState.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/Enum/AudioPlayState.swift new file mode 100644 index 00000000..05b18a6b --- /dev/null +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/Enum/AudioPlayState.swift @@ -0,0 +1,4 @@ +enum AudioPlayState { + case play + case pause +} diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift new file mode 100644 index 00000000..4f2fa58f --- /dev/null +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift @@ -0,0 +1,131 @@ +import UIKit +import Combine +import AVFAudio +import MHCore + + +final public class AudioPlayerView: UIView { + + // MARK: - Property + // data bind + private var viewModel: AudioPlayerViewModel? + private let input = PassthroughSubject() + private var cancellables = Set() + // audio + let audioPlayer: AVAudioPlayer? + var audioPlayState: AudioPlayState + + // MARK: - ViewComponent + let audioProgressView: UIView = { + let backgroundView = UIView() + backgroundView.backgroundColor = .mhPink + backgroundView.layer.cornerRadius = 5 + + return backgroundView + }() + let audioStateButton: UIButton = { + let button = UIButton() + return button + }() + let playImage = UIImage(systemName: "play.fill") + let pauseImage = UIImage(systemName: "pause.fill") + let audioStateImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "audio_play") + + return imageView + }() + let audioPlayTimeLabel: UILabel = { + let label = UILabel() + label.text = "00:00" + label.font = UIFont.ownglyphBerry(size: 14) + label.textAlignment = .left + label.textColor = .black + + return label + }() + + public override init(frame: CGRect) { + audioPlayer = try? AVAudioPlayer(contentsOf: .documentsDirectory.appendingPathComponent("audio.m4a")) + super.init(frame: frame) + } + + public required init?(coder: NSCoder) { + audioPlayer = try? AVAudioPlayer(contentsOf: .documentsDirectory.appendingPathComponent("audio.m4a")) + super.init(frame: .zero) + } + + // MARK: - setup + private func setup() { + backgroundColor = .baseBackground + layer.cornerRadius = 5 + } + + // MARK: - bind + private func bind() { + let output = viewModel?.transform(input: input.eraseToAnyPublisher()) + output?.sink(receiveValue: { [weak self] event in + switch event { + case .getAudioState(let state): + self?.updateAudioPlayImage(audioPlayState: state) + } + }).store(in: &cancellables) + } + + private func configureAddSubview() { + addSubview(audioProgressView) + addSubview(audioStateButton) + addSubview(audioPlayTimeLabel) + } + + private func configureContstraints() { + audioProgressView.setAnchor( + top: topAnchor, + leading: leadingAnchor, + bottom: bottomAnchor, + width: 10 + ) + + audioStateButton.setAnchor( + top: topAnchor, + leading: leadingAnchor, + bottom: bottomAnchor, + width: frame.height + ) + + audioPlayTimeLabel.setAnchor( + top: topAnchor, + leading: audioStateButton.trailingAnchor, + bottom: bottomAnchor, + width: frame.height * 2 + ) + } + + private func updateAudioPlayImage(audioPlayState state: AudioPlayState) { + switch state { + case .play: + audioStateButton.setImage(playImage, for: .normal) + audioPlayer?.play() + audioPlayState = .play + case .pause: + audioStateButton.setImage(pauseImage, for: .normal) + audioPlayer?.pause() + audioPlayState = .pause + } + } + + private func playAudioProgress() { + audioProgressView.setAnchor( + top: topAnchor, + leading: leadingAnchor, + bottom: bottomAnchor, + width: (audioPlayer?.currentTime / audioPlayer?.duration) * frame.width + ) + } +} + +extension AudioPlayerView: AVAudioPlayerDelegate { + nonisolated public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + MHLogger.debug("audio player finished") + } +} diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/ViewModel/AudioPlayerViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/ViewModel/AudioPlayerViewModel.swift new file mode 100644 index 00000000..f59f4832 --- /dev/null +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/ViewModel/AudioPlayerViewModel.swift @@ -0,0 +1,38 @@ +import MHFoundation +import Combine + +final public class AudioPlayerViewModel: ViewModelType { + enum Input { + case audioStateButtonTapped + } + enum Output { + case getAudioState(AudioPlayState) + } + + private let output = PassthroughSubject() + private var cancellables = Set() + private var audioPlayState: AudioPlayState = .pause + + func transform(input: AnyPublisher) -> AnyPublisher { + input.sink { [weak self] event in + switch event { + case .audioStateButtonTapped: + self?.audioStateChanged() + } + }.store(in: &cancellables) + + return output.eraseToAnyPublisher() + } + + private func audioStateChanged() { + switch audioPlayState { + case .pause: + audioPlayState = .play + output.send(.getAudioState(.play)) + case .play: + audioPlayState = .pause + output.send(.getAudioState(.pause)) + } + return + } +} From 32890b832b445cef6c6effc853f7e0969e2dc0fb Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Mon, 2 Dec 2024 17:07:14 +0900 Subject: [PATCH 02/35] feat: add action to push audiocontroller when audio button tapped --- .../Source/EditBook/View/EditBookViewController.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift index 50c270b6..770b696a 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift @@ -229,6 +229,16 @@ final class EditBookViewController: UIViewController { let addAudioAction = UIAction { [weak self] _ in // TODO: - 오디오 추가 로직 + let audioViewModel = CreateAudioViewModel() + let audioViewController = CreateAudioViewController(viewModel: audioViewModel) + if let sheet = audioViewController.sheetPresentationController { + sheet.detents = [.custom { + detent in 0.35 * detent.maximumDetentValue + }] + sheet.prefersGrabberVisible = true + } + + self?.present(audioViewController, animated: true) } addAudioButton.addAction(addAudioAction, for: .touchUpInside) From b87ac6c907196c2880458a231a7bda8dc460b71d Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Mon, 2 Dec 2024 20:02:23 +0900 Subject: [PATCH 03/35] chore: file system access setting --- MemorialHouse/MHApplication/MHApplication/Resource/Info.plist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist index 0eb786dc..abd27cfb 100644 --- a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist +++ b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist @@ -2,6 +2,10 @@ + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes From ba4e4dcfb50f48de50a2858fb1a369f303f6a478 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Mon, 2 Dec 2024 20:04:18 +0900 Subject: [PATCH 04/35] feat: send uuid (audio vc -> editbook vc) --- .../Audio/Audio/View/AudioViewController.swift | 12 +++++++++--- .../EditBook/View/EditBookViewController.swift | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift index 02ee8eb9..ca1993a7 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift @@ -31,6 +31,7 @@ final public class CreateAudioViewController: UIViewController { ] // UUID private let identifier: UUID = UUID() + var audioCreationCompletion: (((UUID?) -> Void))? // MARK: - UI Component // title and buttons @@ -376,9 +377,14 @@ final public class CreateAudioViewController: UIViewController { for: .touchUpInside) } private func addTappedEventToSaveButton() { - saveButton.addAction(UIAction { _ in - self.input.send(.saveButtonTapped) - self.dismiss(animated: true) + saveButton.addAction(UIAction { [weak self] _ in + self?.completeAudioCreation(uuid: self?.identifier) }, for: .touchUpInside) } + + private func completeAudioCreation(uuid: UUID?) { + self.input.send(.saveButtonTapped) + dismiss(animated: true) + audioCreationCompletion?(uuid) + } } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift index 770b696a..8fa7368c 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift @@ -231,10 +231,12 @@ final class EditBookViewController: UIViewController { // TODO: - 오디오 추가 로직 let audioViewModel = CreateAudioViewModel() let audioViewController = CreateAudioViewController(viewModel: audioViewModel) + + audioViewController.audioCreationCompletion = { uuid in + MHLogger.debug(uuid) + } if let sheet = audioViewController.sheetPresentationController { - sheet.detents = [.custom { - detent in 0.35 * detent.maximumDetentValue - }] + sheet.detents = [.custom { detent in 0.35 * detent.maximumDetentValue }] sheet.prefersGrabberVisible = true } From 52ab72f6ad0350b74a86b1edc80382bc2750337a Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 02:25:29 +0900 Subject: [PATCH 05/35] feat: add media view factory --- .../Source/EditBook/View/EditPageCell.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift index eae071b7..2f279005 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift @@ -151,7 +151,7 @@ final class EditPageCell: UITableViewCell { } private func mediaAddedWithURL(media: MediaDescription, url: URL) { let attachment = MediaAttachment( - view: MHPolaroidPhotoView(),// TODO: - 수정 필요 + view: mediaViewFactory(type: media.type),// TODO: - 수정 필요 description: media ) attachment.configure(with: url) @@ -185,6 +185,19 @@ final class EditPageCell: UITableViewCell { } return attachment } + + private func mediaViewFactory(type: MediaType) -> UIView & MediaAttachable { + switch type { + case .image: + MHPolaroidPhotoView() + case .video: + MHPolaroidPhotoView() + case .audio: + MHAudioPlayerView() + default: + MHPolaroidPhotoView() + } + } } // MARK: - MediaAttachmentDataSource From f9a79757d0e7ed3255b4a31ed72f995f930db09a Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 02:26:01 +0900 Subject: [PATCH 06/35] feat: uuid to url --- .../Source/EditBook/View/EditBookViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift index 8fa7368c..a961130b 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift @@ -232,8 +232,10 @@ final class EditBookViewController: UIViewController { let audioViewModel = CreateAudioViewModel() let audioViewController = CreateAudioViewController(viewModel: audioViewModel) - audioViewController.audioCreationCompletion = { uuid in - MHLogger.debug(uuid) + audioViewController.audioCreationCompletion = { url in + guard let url else { return } + MHLogger.debug(url) + self?.input.send(.didAddMediaInURL(type: .audio, url: url)) } if let sheet = audioViewController.sheetPresentationController { sheet.detents = [.custom { detent in 0.35 * detent.maximumDetentValue }] From 7be66995871bda998830cfc4dadfb8bcba8a0980 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 03:19:45 +0900 Subject: [PATCH 07/35] feat: audio player logic --- ...ayerView.swift => MHAudioPlayerView.swift} | 97 +++++++++++++++---- 1 file changed, 78 insertions(+), 19 deletions(-) rename MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/{AudioPlayerView.swift => MHAudioPlayerView.swift} (52%) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift similarity index 52% rename from MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift rename to MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index 4f2fa58f..213945a3 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/AudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -2,18 +2,18 @@ import UIKit import Combine import AVFAudio import MHCore +import MHDomain -final public class AudioPlayerView: UIView { - +final public class MHAudioPlayerView: UIView { // MARK: - Property // data bind private var viewModel: AudioPlayerViewModel? private let input = PassthroughSubject() private var cancellables = Set() // audio - let audioPlayer: AVAudioPlayer? - var audioPlayState: AudioPlayState + var audioPlayer: AVAudioPlayer? + var audioPlayState: AudioPlayState = .pause // MARK: - ViewComponent let audioProgressView: UIView = { @@ -25,6 +25,7 @@ final public class AudioPlayerView: UIView { }() let audioStateButton: UIButton = { let button = UIButton() + button.setImage(UIImage(systemName: "play.fill"), for: .normal) return button }() let playImage = UIImage(systemName: "play.fill") @@ -46,19 +47,27 @@ final public class AudioPlayerView: UIView { }() public override init(frame: CGRect) { - audioPlayer = try? AVAudioPlayer(contentsOf: .documentsDirectory.appendingPathComponent("audio.m4a")) super.init(frame: frame) + + setup() + bind() + configureAddSubview() + configureContstraints() + configureAddActions() } public required init?(coder: NSCoder) { - audioPlayer = try? AVAudioPlayer(contentsOf: .documentsDirectory.appendingPathComponent("audio.m4a")) super.init(frame: .zero) } // MARK: - setup private func setup() { - backgroundColor = .baseBackground + backgroundColor = .blue layer.cornerRadius = 5 + + let audioSession = AVAudioSession.sharedInstance() + try? audioSession.setCategory(.playback, mode: .default, options: []) + try? audioSession.setActive(true) } // MARK: - bind @@ -80,37 +89,61 @@ final public class AudioPlayerView: UIView { private func configureContstraints() { audioProgressView.setAnchor( - top: topAnchor, + top: topAnchor, constantTop: 10, leading: leadingAnchor, - bottom: bottomAnchor, - width: 10 + bottom: bottomAnchor, constantBottom: 10, + width: 50 ) audioStateButton.setAnchor( top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, - width: frame.height + width: 80 ) audioPlayTimeLabel.setAnchor( top: topAnchor, leading: audioStateButton.trailingAnchor, bottom: bottomAnchor, - width: frame.height * 2 + width: 160 ) } + private func configureAddActions() { + audioStateButton.addAction(UIAction { [weak self] _ in + guard let audioPlayState = self?.audioPlayState else { return } + self?.updateAudioPlayImage(audioPlayState: audioPlayState) + + self?.input.send(.audioStateButtonTapped) + }, for: .touchUpInside) + } + private func updateAudioPlayImage(audioPlayState state: AudioPlayState) { switch state { case .play: + MHLogger.debug("pause") audioStateButton.setImage(playImage, for: .normal) - audioPlayer?.play() - audioPlayState = .play - case .pause: - audioStateButton.setImage(pauseImage, for: .normal) audioPlayer?.pause() audioPlayState = .pause + case .pause: + audioStateButton.setImage(pauseImage, for: .normal) + if audioPlayer?.play() == false { + MHLogger.error("do not play") + } else { + MHLogger.debug("do play") + } + audioPlayState = .play + +// Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in +// Task { @MainActor in +// guard let player = self?.audioPlayer else { +// print("audio player not init") +// return } +// print("Current playback time: \(player.currentTime) seconds") +// +// } +// } } } @@ -118,14 +151,40 @@ final public class AudioPlayerView: UIView { audioProgressView.setAnchor( top: topAnchor, leading: leadingAnchor, - bottom: bottomAnchor, - width: (audioPlayer?.currentTime / audioPlayer?.duration) * frame.width + bottom: bottomAnchor + // TODO: - width ) } + + private func setTimeLabel(seconds recordingSeconds: Int?) { + guard let recordingSeconds = recordingSeconds else { return } + let minutes = recordingSeconds / 60 + let seconds = recordingSeconds % 60 + audioPlayTimeLabel.text = String(format: "%02d:%02d", minutes, seconds) + } } -extension AudioPlayerView: AVAudioPlayerDelegate { +extension MHAudioPlayerView: AVAudioPlayerDelegate { nonisolated public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { MHLogger.debug("audio player finished") + + Task { @MainActor in + self.audioPlayState = .pause + self.audioStateButton.setImage(playImage, for: .normal) + } + } +} + +extension MHAudioPlayerView: @preconcurrency MediaAttachable { + func configureSource(with mediaDescription: MediaDescription, data: Data) { + + } + + func configureSource(with mediaDescription: MediaDescription, url: URL) { + MHLogger.debug("configure source \(url)") + audioPlayer = try? AVAudioPlayer(contentsOf: url) + guard let audioPlayer else { return } + audioPlayer.delegate = self + self.setTimeLabel(seconds: Int(audioPlayer.duration.rounded())) } } From 25ee31baa0de3928a6d42bed20e3027d55594d88 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 04:55:02 +0900 Subject: [PATCH 08/35] feat: process view design --- .../Audio/Player/View/MHAudioPlayerView.swift | 116 +++++++++++++----- 1 file changed, 84 insertions(+), 32 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index 213945a3..a5ac53b9 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -12,17 +12,39 @@ final public class MHAudioPlayerView: UIView { private let input = PassthroughSubject() private var cancellables = Set() // audio - var audioPlayer: AVAudioPlayer? - var audioPlayState: AudioPlayState = .pause + nonisolated(unsafe) var audioPlayer: AVAudioPlayer? + var audioPlayState: AudioPlayState = .pause { + didSet { + switch audioPlayState { + case .play: + startTimer() + case .pause: + stopTimer() + } + } + } + var timer: Timer? // MARK: - ViewComponent + let backgroundBorderView: UIView = { + let backgroundBorderView = UIView() + backgroundBorderView.backgroundColor = .baseBackground + backgroundBorderView.layer.borderWidth = 4 + backgroundBorderView.layer.cornerRadius = 25 + backgroundBorderView.layer.borderColor = UIColor.captionPlaceHolder.cgColor + + return backgroundBorderView + }() let audioProgressView: UIView = { let backgroundView = UIView() backgroundView.backgroundColor = .mhPink - backgroundView.layer.cornerRadius = 5 +// backgroundView.layer.borderWidth = 4 + backgroundView.layer.cornerRadius = 21 +// backgroundView.layer.borderColor = UIColor.baseBackground.cgColor return backgroundView }() + var progressViewConstraints: [NSLayoutConstraint] = [] let audioStateButton: UIButton = { let button = UIButton() button.setImage(UIImage(systemName: "play.fill"), for: .normal) @@ -39,9 +61,9 @@ final public class MHAudioPlayerView: UIView { let audioPlayTimeLabel: UILabel = { let label = UILabel() label.text = "00:00" - label.font = UIFont.ownglyphBerry(size: 14) + label.font = UIFont.ownglyphBerry(size: 21) label.textAlignment = .left - label.textColor = .black + label.textColor = .dividedLine return label }() @@ -62,9 +84,7 @@ final public class MHAudioPlayerView: UIView { // MARK: - setup private func setup() { - backgroundColor = .blue - layer.cornerRadius = 5 - + backgroundColor = .baseBackground let audioSession = AVAudioSession.sharedInstance() try? audioSession.setCategory(.playback, mode: .default, options: []) try? audioSession.setActive(true) @@ -82,31 +102,39 @@ final public class MHAudioPlayerView: UIView { } private func configureAddSubview() { + addSubview(backgroundBorderView) addSubview(audioProgressView) addSubview(audioStateButton) addSubview(audioPlayTimeLabel) } private func configureContstraints() { - audioProgressView.setAnchor( - top: topAnchor, constantTop: 10, + backgroundBorderView.setAnchor( + top: topAnchor, constantTop: 25, leading: leadingAnchor, - bottom: bottomAnchor, constantBottom: 10, - width: 50 + bottom: bottomAnchor, constantBottom: 25, + trailing: trailingAnchor + ) + + audioProgressView.setAnchor( + top: topAnchor, constantTop: 29, + leading: leadingAnchor, constantLeading: 4, + bottom: bottomAnchor, constantBottom: 29, + width: 0 ) audioStateButton.setAnchor( - top: topAnchor, + top: topAnchor, constantTop: 25, leading: leadingAnchor, - bottom: bottomAnchor, - width: 80 + bottom: bottomAnchor, constantBottom: 25, + width: 50 ) audioPlayTimeLabel.setAnchor( - top: topAnchor, + top: topAnchor, constantTop: 25, leading: audioStateButton.trailingAnchor, - bottom: bottomAnchor, - width: 160 + bottom: bottomAnchor, constantBottom: 25, + width: 100 ) } @@ -134,26 +162,40 @@ final public class MHAudioPlayerView: UIView { MHLogger.debug("do play") } audioPlayState = .play - -// Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in -// Task { @MainActor in -// guard let player = self?.audioPlayer else { -// print("audio player not init") -// return } -// print("Current playback time: \(player.currentTime) seconds") -// -// } -// } } } - private func playAudioProgress() { + private func updatePlayAudioProgress() { + guard let audioPlayer else { return } + let width = ceil(Float(audioPlayer.currentTime) / Float(audioPlayer.duration) * Float(299)) + NSLayoutConstraint.deactivate(audioProgressView.constraints) + audioProgressView.setAnchor( - top: topAnchor, - leading: leadingAnchor, - bottom: bottomAnchor + top: topAnchor, constantTop: 29, + leading: leadingAnchor, constantLeading: 4, + bottom: bottomAnchor, constantBottom: 29, // TODO: - width + width: CGFloat(width) ) + NSLayoutConstraint.activate(audioProgressView.constraints) + } + + private func startTimer() { + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in + guard let audioPlayer = self?.audioPlayer else { return } + Task { @MainActor in + if audioPlayer.isPlaying { + self?.updatePlayAudioProgress() + } else { + + } + } + } + } + + private func stopTimer() { + } private func setTimeLabel(seconds recordingSeconds: Int?) { @@ -171,6 +213,16 @@ extension MHAudioPlayerView: AVAudioPlayerDelegate { Task { @MainActor in self.audioPlayState = .pause self.audioStateButton.setImage(playImage, for: .normal) + + NSLayoutConstraint.deactivate(audioProgressView.constraints) + audioProgressView.setAnchor( + top: topAnchor, constantTop: 29, + leading: leadingAnchor, constantLeading: 4, + bottom: bottomAnchor, constantBottom: 29, + // TODO: - width + width: 290 + ) + NSLayoutConstraint.activate(audioProgressView.constraints) } } } From df027f80fd4e95c1384abf7858eb89ab37708cae Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 05:55:02 +0900 Subject: [PATCH 09/35] fix: autolayout error --- .../Audio/Player/View/MHAudioPlayerView.swift | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index a5ac53b9..058d6ceb 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -44,6 +44,7 @@ final public class MHAudioPlayerView: UIView { return backgroundView }() + var progressViewWidthConstraint: NSLayoutConstraint? var progressViewConstraints: [NSLayoutConstraint] = [] let audioStateButton: UIButton = { let button = UIButton() @@ -116,12 +117,14 @@ final public class MHAudioPlayerView: UIView { trailing: trailingAnchor ) - audioProgressView.setAnchor( - top: topAnchor, constantTop: 29, - leading: leadingAnchor, constantLeading: 4, - bottom: bottomAnchor, constantBottom: 29, - width: 0 - ) + audioProgressView.translatesAutoresizingMaskIntoConstraints = false + progressViewWidthConstraint = audioProgressView.widthAnchor.constraint(equalToConstant: 0) + NSLayoutConstraint.activate([ + audioProgressView.topAnchor.constraint(equalTo: topAnchor, constant: 29), + audioProgressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4), + audioProgressView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -29), + progressViewWidthConstraint ?? audioProgressView.widthAnchor.constraint(equalToConstant: 0) + ]) audioStateButton.setAnchor( top: topAnchor, constantTop: 25, @@ -168,16 +171,11 @@ final public class MHAudioPlayerView: UIView { private func updatePlayAudioProgress() { guard let audioPlayer else { return } let width = ceil(Float(audioPlayer.currentTime) / Float(audioPlayer.duration) * Float(299)) - NSLayoutConstraint.deactivate(audioProgressView.constraints) - audioProgressView.setAnchor( - top: topAnchor, constantTop: 29, - leading: leadingAnchor, constantLeading: 4, - bottom: bottomAnchor, constantBottom: 29, - // TODO: - width - width: CGFloat(width) - ) - NSLayoutConstraint.activate(audioProgressView.constraints) + progressViewWidthConstraint?.constant = CGFloat(width) + UIView.animate(withDuration: 0) { + self.layoutIfNeeded() + } } private func startTimer() { @@ -195,7 +193,8 @@ final public class MHAudioPlayerView: UIView { } private func stopTimer() { - + timer?.invalidate() + timer = nil } private func setTimeLabel(seconds recordingSeconds: Int?) { @@ -214,15 +213,12 @@ extension MHAudioPlayerView: AVAudioPlayerDelegate { self.audioPlayState = .pause self.audioStateButton.setImage(playImage, for: .normal) - NSLayoutConstraint.deactivate(audioProgressView.constraints) - audioProgressView.setAnchor( - top: topAnchor, constantTop: 29, - leading: leadingAnchor, constantLeading: 4, - bottom: bottomAnchor, constantBottom: 29, - // TODO: - width - width: 290 - ) - NSLayoutConstraint.activate(audioProgressView.constraints) + stopTimer() + + progressViewWidthConstraint?.constant = CGFloat(290) + UIView.animate(withDuration: 0) { + self.layoutIfNeeded() + } } } } From 1620cbeed9376777885e14fa5dc3f47e3df6d25d Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 11:06:39 +0900 Subject: [PATCH 10/35] feat: player style changed --- .../Source/Audio/Player/View/MHAudioPlayerView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index 058d6ceb..c6fe4cbe 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -29,7 +29,7 @@ final public class MHAudioPlayerView: UIView { let backgroundBorderView: UIView = { let backgroundBorderView = UIView() backgroundBorderView.backgroundColor = .baseBackground - backgroundBorderView.layer.borderWidth = 4 + backgroundBorderView.layer.borderWidth = 3 backgroundBorderView.layer.cornerRadius = 25 backgroundBorderView.layer.borderColor = UIColor.captionPlaceHolder.cgColor @@ -118,7 +118,7 @@ final public class MHAudioPlayerView: UIView { ) audioProgressView.translatesAutoresizingMaskIntoConstraints = false - progressViewWidthConstraint = audioProgressView.widthAnchor.constraint(equalToConstant: 0) + progressViewWidthConstraint = audioProgressView.widthAnchor.constraint(equalToConstant: 290) NSLayoutConstraint.activate([ audioProgressView.topAnchor.constraint(equalTo: topAnchor, constant: 29), audioProgressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4), @@ -185,6 +185,7 @@ final public class MHAudioPlayerView: UIView { Task { @MainActor in if audioPlayer.isPlaying { self?.updatePlayAudioProgress() + self?.setTimeLabel(seconds: Int(audioPlayer.currentTime)) } else { } From 19393ba3960ef669cb464b1770594f1fb2c7e2ab Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 11:07:09 +0900 Subject: [PATCH 11/35] feat: uuid -> url --- .../Audio/Audio/View/AudioViewController.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift index ca1993a7..2cacafb7 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift @@ -31,7 +31,7 @@ final public class CreateAudioViewController: UIViewController { ] // UUID private let identifier: UUID = UUID() - var audioCreationCompletion: (((UUID?) -> Void))? + var audioCreationCompletion: (((URL?) -> Void))? // MARK: - UI Component // title and buttons @@ -371,20 +371,25 @@ final public class CreateAudioViewController: UIViewController { } private func addTappedEventToCancelButton() { cancelButton.addAction( - UIAction { [weak self]_ in + UIAction { [weak self] _ in self?.dismiss(animated: true) }, for: .touchUpInside) } private func addTappedEventToSaveButton() { - saveButton.addAction(UIAction { [weak self] _ in - self?.completeAudioCreation(uuid: self?.identifier) + saveButton.addAction( + UIAction { [weak self] _ in + self?.completeAudioCreation(uuid: self?.identifier) }, for: .touchUpInside) } private func completeAudioCreation(uuid: UUID?) { + guard let uuid else { return } + let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let filePath = documentPath.appendingPathComponent("\(uuid).m4a") + self.input.send(.saveButtonTapped) dismiss(animated: true) - audioCreationCompletion?(uuid) + audioCreationCompletion?(filePath) } } From dda2954b2c1260a09c81feeafc9f03a14c878892 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 15:22:33 +0900 Subject: [PATCH 12/35] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=99=80=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{AudioViewController.swift => CreateAudioViewController.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/{AudioViewController.swift => CreateAudioViewController.swift} (100%) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift similarity index 100% rename from MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/AudioViewController.swift rename to MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift From dbc0862d626aed0597f1d1118f2e06e708bdba9d Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 15:38:54 +0900 Subject: [PATCH 13/35] =?UTF-8?q?refactor:=20=EB=A7=88=EC=9D=B4=ED=81=AC?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EB=B2=84=EC=A0=84=EB=B3=84=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 2cacafb7..926f71cd 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -159,17 +159,23 @@ final public class CreateAudioViewController: UIViewController { } private func requestMicrophonePermission() { - AVAudioSession.sharedInstance().requestRecordPermission { @Sendable granted in - if !granted { - Task { @MainActor in - let alert = UIAlertController( - title: "마이크 권한 필요", - message: "설정에서 마이크 권한을 허용해주세요.", - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "OK", style: .default)) + let alert = UIAlertController( + title: "마이크 권한 필요", + message: "설정에서 마이크 권한을 허용해주세요.", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + Task { + if #available(iOS 17, *) { + if await !AVAudioApplication.requestRecordPermission() { self.present(alert, animated: true, completion: nil) } + } else { + AVAudioSession.sharedInstance().requestRecordPermission { @MainActor granted in + if !granted { + self.present(alert, animated: true, completion: nil) + } + } } } } From 77e3b9a499a2e80f3b103fd18fa843a27edc8742 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 15:46:27 +0900 Subject: [PATCH 14/35] =?UTF-8?q?refactor:=20public=EC=A0=9C=EA=B1=B0,=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9C=84=EC=B9=98=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 926f71cd..7796d449 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -3,7 +3,7 @@ import AVFoundation import Combine import MHCore -final public class CreateAudioViewController: UIViewController { +final class CreateAudioViewController: UIViewController { // MARK: - Property // data bind private var viewModel: CreateAudioViewModel? @@ -96,18 +96,18 @@ final public class CreateAudioViewController: UIViewController { }() // MARK: - Initializer - public init(viewModel: CreateAudioViewModel) { + init(viewModel: CreateAudioViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } - public required init?(coder: NSCoder) { + required init?(coder: NSCoder) { self.viewModel = CreateAudioViewModel() super.init(nibName: nil, bundle: nil) } // MARK: - Life Cycle - public override func viewDidLoad() { + override func viewDidLoad() { super.viewDidLoad() setup() @@ -118,11 +118,11 @@ final public class CreateAudioViewController: UIViewController { configureAddActions() } - public override func viewDidDisappear(_ animated: Bool) { + override func viewDidDisappear(_ animated: Bool) { self.input.send(.viewDidDisappear) } - // MARK: - setup + // MARK: - Setup private func setup() { view.backgroundColor = .white setupBars() @@ -158,28 +158,6 @@ final public class CreateAudioViewController: UIViewController { } } - private func requestMicrophonePermission() { - let alert = UIAlertController( - title: "마이크 권한 필요", - message: "설정에서 마이크 권한을 허용해주세요.", - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - Task { - if #available(iOS 17, *) { - if await !AVAudioApplication.requestRecordPermission() { - self.present(alert, animated: true, completion: nil) - } - } else { - AVAudioSession.sharedInstance().requestRecordPermission { @MainActor granted in - if !granted { - self.present(alert, animated: true, completion: nil) - } - } - } - } - } - // MARK: - bind private func bind() { let output = viewModel?.transform(input: input.eraseToAnyPublisher()) @@ -202,8 +180,7 @@ final public class CreateAudioViewController: UIViewController { }).store(in: &cancellables) } - // MARK: - configure - + // MARK: - Configuration private func configureAudioSession() { let fileName = "\(identifier).m4a" let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] @@ -270,6 +247,29 @@ final public class CreateAudioViewController: UIViewController { timeTextLabel.setWidthAndHeight(width: 60, height: 16) } + // MARK: - Helper + private func requestMicrophonePermission() { + let alert = UIAlertController( + title: "마이크 권한 필요", + message: "설정에서 마이크 권한을 허용해주세요.", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + Task { + if #available(iOS 17, *) { + if await !AVAudioApplication.requestRecordPermission() { + self.present(alert, animated: true, completion: nil) + } + } else { + AVAudioSession.sharedInstance().requestRecordPermission { @MainActor granted in + if !granted { + self.present(alert, animated: true, completion: nil) + } + } + } + } + } + private func startRecording() { try? audioSession.setActive(true) From 9b0d6a9cfaf12222bbfc4755ab1eb8f1abc49b10 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 15:53:38 +0900 Subject: [PATCH 15/35] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 41 +++++++++---------- .../ViewModel/CreateAudioViewModel.swift | 12 ++++-- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 7796d449..f7da303b 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -11,7 +11,6 @@ final class CreateAudioViewController: UIViewController { private var cancellables = Set() // auido private var audioRecorder: AVAudioRecorder? - private var isRecording = false // auido metering private var upBarLayers: [CALayer] = [] private var downBarLayers: [CALayer] = [] @@ -29,9 +28,6 @@ final class CreateAudioViewController: UIViewController { AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] - // UUID - private let identifier: UUID = UUID() - var audioCreationCompletion: (((URL?) -> Void))? // MARK: - UI Component // title and buttons @@ -161,23 +157,24 @@ final class CreateAudioViewController: UIViewController { // MARK: - bind private func bind() { let output = viewModel?.transform(input: input.eraseToAnyPublisher()) - output?.sink(receiveValue: { [weak self] event in - switch event { - case .updatedAudioFileURL: - // TODO: - update audio file url - MHLogger.debug("updated audio file URL") - case .savedAudioFile: - // TODO: - show audio player - MHLogger.debug("saved audio file") - case .deleteTemporaryAudioFile: - // TODO: - delete temporary audio file - MHLogger.debug("delete temporary audio file") - case .audioStart: - self?.startRecording() - case .audioStop: - self?.stopRecording() - } - }).store(in: &cancellables) + output?.receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] event in + switch event { + case .updatedAudioFileURL: + // TODO: - update audio file url + MHLogger.debug("updated audio file URL") + case .savedAudioFile: + // TODO: - show audio player + MHLogger.debug("saved audio file") + case .deleteTemporaryAudioFile: + // TODO: - delete temporary audio file + MHLogger.debug("delete temporary audio file") + case .audioStart: + self?.startRecording() + case .audioStop: + self?.stopRecording() + } + }).store(in: &cancellables) } // MARK: - Configuration @@ -261,7 +258,7 @@ final class CreateAudioViewController: UIViewController { self.present(alert, animated: true, completion: nil) } } else { - AVAudioSession.sharedInstance().requestRecordPermission { @MainActor granted in + AVAudioSession.sharedInstance().requestRecordPermission { granted in if !granted { self.present(alert, animated: true, completion: nil) } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift index 21859780..a8a5bb4c 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift @@ -2,7 +2,8 @@ import Foundation import Combine import MHCore -public final class CreateAudioViewModel: ViewModelType { +final class CreateAudioViewModel: ViewModelType { + // MARK: - Type enum Input { case audioSessionOpened(url: URL?) case audioButtonTapped @@ -17,12 +18,16 @@ public final class CreateAudioViewModel: ViewModelType { case deleteTemporaryAudioFile } + // MARK: - Property private let output = PassthroughSubject() private var cancellables = Set() - private var identifier: UUID? - private var audioTemporaryFileURL: URL? private var audioIsRecoding: Bool = false + private let completion: (URL?) -> Void + // MARK: - Initializer + + + // MARK: - Method func transform(input: AnyPublisher) -> AnyPublisher { input.sink { [weak self] event in switch event { @@ -40,6 +45,7 @@ public final class CreateAudioViewModel: ViewModelType { return output.eraseToAnyPublisher() } + // MARK: - Helper private func updateURL(url: URL?) { self.audioTemporaryFileURL = url output.send(.updatedAudioFileURL) From e4bb2298db55985f95dd008f7ac7e3bf20ca2b05 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 16:17:00 +0900 Subject: [PATCH 16/35] =?UTF-8?q?feat:=20viewLoad=EB=90=90=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 18 +++----- .../ViewModel/CreateAudioViewModel.swift | 41 +++++++++---------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index f7da303b..ce2abe05 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -98,7 +98,7 @@ final class CreateAudioViewController: UIViewController { } required init?(coder: NSCoder) { - self.viewModel = CreateAudioViewModel() + self.viewModel = CreateAudioViewModel(forBookID: .init(), completion: { _ in }) super.init(nibName: nil, bundle: nil) } @@ -160,15 +160,11 @@ final class CreateAudioViewController: UIViewController { output?.receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] event in switch event { - case .updatedAudioFileURL: - // TODO: - update audio file url - MHLogger.debug("updated audio file URL") + case let .audioFileURL(url): + self?.configureAudioSession(for: url) case .savedAudioFile: // TODO: - show audio player MHLogger.debug("saved audio file") - case .deleteTemporaryAudioFile: - // TODO: - delete temporary audio file - MHLogger.debug("delete temporary audio file") case .audioStart: self?.startRecording() case .audioStop: @@ -178,14 +174,10 @@ final class CreateAudioViewController: UIViewController { } // MARK: - Configuration - private func configureAudioSession() { - let fileName = "\(identifier).m4a" - let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - let audioFileURL = documentDirectory.appendingPathComponent(fileName) - + private func configureAudioSession(for url: URL) { try? audioSession.setCategory(.record, mode: .default) - audioRecorder = try? AVAudioRecorder(url: audioFileURL, settings: audioRecordersettings) + audioRecorder = try? AVAudioRecorder(url: url, settings: audioRecordersettings) audioRecorder?.isMeteringEnabled = true } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift index a8a5bb4c..501b2cba 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift @@ -1,44 +1,45 @@ import Foundation +import MHData import Combine import MHCore final class CreateAudioViewModel: ViewModelType { // MARK: - Type enum Input { - case audioSessionOpened(url: URL?) + case viewDidLoad case audioButtonTapped case saveButtonTapped - case viewDidDisappear } enum Output { - case updatedAudioFileURL + case audioFileURL(url: URL) case audioStart case audioStop case savedAudioFile - case deleteTemporaryAudioFile } // MARK: - Property private let output = PassthroughSubject() private var cancellables = Set() private var audioIsRecoding: Bool = false - private let completion: (URL?) -> Void + private let completion: (Result) -> Void + private let forBookID: UUID // MARK: - Initializer - + init(forBookID: UUID, completion: @escaping (Result) -> Void) { + self.forBookID = forBookID + self.completion = completion + } // MARK: - Method func transform(input: AnyPublisher) -> AnyPublisher { input.sink { [weak self] event in switch event { - case .audioSessionOpened(let url): - self?.updateURL(url: url) + case .viewDidLoad: + Task { await self?.viewDidLoad() } case .audioButtonTapped: self?.audioButtonTapped() case .saveButtonTapped: self?.saveAudioFile() - case .viewDidDisappear: - self?.deleteAudioTemporaryFile() } }.store(in: &cancellables) @@ -46,13 +47,17 @@ final class CreateAudioViewModel: ViewModelType { } // MARK: - Helper - private func updateURL(url: URL?) { - self.audioTemporaryFileURL = url - output.send(.updatedAudioFileURL) + private func viewDidLoad() async { + do { + let url = try await MHFileManager(directoryType: .documentDirectory) + .getURL(at: forBookID.uuidString, fileName: "temp.m4a") + .get() + output.send(.audioFileURL(url: url)) + } catch { + MHLogger.error("Error in getting audio file url: \(error.localizedDescription)") + } } - private func audioButtonTapped() { - MHLogger.debug("audio button tapped in view model") switch audioIsRecoding { case false: output.send(.audioStart) @@ -66,10 +71,4 @@ final class CreateAudioViewModel: ViewModelType { // TODO: - save audio file in the file system output.send(.savedAudioFile) } - - private func deleteAudioTemporaryFile() { - guard let audioTemporaryFileURL else { return } - try? FileManager.default.removeItem(at: audioTemporaryFileURL) - output.send(.deleteTemporaryAudioFile) - } } From e45f320b5a8637d26cdab3b65e4c015718881097 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 16:53:00 +0900 Subject: [PATCH 17/35] =?UTF-8?q?refactor:=20mainActor=EB=A1=9C=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EB=AC=BB=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Audio/Audio/View/CreateAudioViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index ce2abe05..589d4763 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -251,8 +251,10 @@ final class CreateAudioViewController: UIViewController { } } else { AVAudioSession.sharedInstance().requestRecordPermission { granted in - if !granted { - self.present(alert, animated: true, completion: nil) + Task { @MainActor in + if !granted { + self.present(alert, animated: true, completion: nil) + } } } } From 8e4ca0a01253c2a53d7de295ccfe77c49fd60efb Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 16:53:22 +0900 Subject: [PATCH 18/35] =?UTF-8?q?refactor:=20bookID=20=EC=97=B4=EA=B3=A0?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/EditBookViewController.swift | 19 +++++++++++-------- .../ViewModel/EditBookViewModel.swift | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift index a961130b..f51a3222 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift @@ -228,21 +228,24 @@ final class EditBookViewController: UIViewController { addVideoButton.addAction(addVideoAction, for: .touchUpInside) let addAudioAction = UIAction { [weak self] _ in - // TODO: - 오디오 추가 로직 - let audioViewModel = CreateAudioViewModel() + guard let self else { return } + let audioViewModel = CreateAudioViewModel( + forBookID: viewModel.bookID) { [weak self] result in + switch result { + case .success(let url): + self?.input.send(.didAddMediaInURL(type: .audio, url: url)) + case .failure(let failure): + return + } + } let audioViewController = CreateAudioViewController(viewModel: audioViewModel) - audioViewController.audioCreationCompletion = { url in - guard let url else { return } - MHLogger.debug(url) - self?.input.send(.didAddMediaInURL(type: .audio, url: url)) - } if let sheet = audioViewController.sheetPresentationController { sheet.detents = [.custom { detent in 0.35 * detent.maximumDetentValue }] sheet.prefersGrabberVisible = true } - self?.present(audioViewController, animated: true) + present(audioViewController, animated: true) } addAudioButton.addAction(addAudioAction, for: .touchUpInside) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift index fce00d40..b81dc41f 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift @@ -27,7 +27,7 @@ final class EditBookViewModel: ViewModelType { private let createMediaUseCase: CreateMediaUseCase private let fetchMediaUseCase: FetchMediaUseCase private let deleteMediaUseCase: DeleteMediaUseCase - private let bookID: UUID + let bookID: UUID private var title: String = "" private var editPageViewModels: [EditPageViewModel] = [] private var currentPageIndex = 0 From a532d6f36e86b66679e9ebc332eac2069ca23d84 Mon Sep 17 00:00:00 2001 From: iceHood Date: Tue, 3 Dec 2024 16:53:53 +0900 Subject: [PATCH 19/35] =?UTF-8?q?refactor:=20=EB=B7=B0=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 69 ++++++++----------- .../ViewModel/CreateAudioViewModel.swift | 22 ++++-- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 589d4763..ccce7d5d 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -108,16 +108,11 @@ final class CreateAudioViewController: UIViewController { setup() bind() - configureAudioSession() configureAddSubviews() configureConstraints() configureAddActions() } - override func viewDidDisappear(_ animated: Bool) { - self.input.send(.viewDidDisappear) - } - // MARK: - Setup private func setup() { view.backgroundColor = .white @@ -162,13 +157,12 @@ final class CreateAudioViewController: UIViewController { switch event { case let .audioFileURL(url): self?.configureAudioSession(for: url) - case .savedAudioFile: - // TODO: - show audio player - MHLogger.debug("saved audio file") case .audioStart: self?.startRecording() case .audioStop: self?.stopRecording() + case .recordCompleted: + self?.dismiss(animated: true) } }).store(in: &cancellables) } @@ -236,6 +230,31 @@ final class CreateAudioViewController: UIViewController { timeTextLabel.setWidthAndHeight(width: 60, height: 16) } + private func configureAddActions() { + addTappedEventToAudioButton() + addTappedEventToCancelButton() + addTappedEventToSaveButton() + } + + private func addTappedEventToAudioButton() { + audioButton.addAction(UIAction { [weak self] _ in + self?.input.send(.audioButtonTapped) + }, for: .touchUpInside) + } + private func addTappedEventToCancelButton() { + cancelButton.addAction( + UIAction { [weak self] _ in + self?.input.send(.recordCancelled) + }, + for: .touchUpInside) + } + private func addTappedEventToSaveButton() { + saveButton.addAction( + UIAction { [weak self] _ in + self?.input.send(.saveButtonTapped) + }, for: .touchUpInside) + } + // MARK: - Helper private func requestMicrophonePermission() { let alert = UIAlertController( @@ -355,38 +374,4 @@ final class CreateAudioViewController: UIViewController { timeTextLabel.text = String(format: "%02d:%02d", minutes, seconds) } - private func configureAddActions() { - addTappedEventToAudioButton() - addTappedEventToCancelButton() - addTappedEventToSaveButton() - } - - private func addTappedEventToAudioButton() { - audioButton.addAction(UIAction { [weak self] _ in - self?.input.send(.audioButtonTapped) - }, for: .touchUpInside) - } - private func addTappedEventToCancelButton() { - cancelButton.addAction( - UIAction { [weak self] _ in - self?.dismiss(animated: true) - }, - for: .touchUpInside) - } - private func addTappedEventToSaveButton() { - saveButton.addAction( - UIAction { [weak self] _ in - self?.completeAudioCreation(uuid: self?.identifier) - }, for: .touchUpInside) - } - - private func completeAudioCreation(uuid: UUID?) { - guard let uuid else { return } - let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - let filePath = documentPath.appendingPathComponent("\(uuid).m4a") - - self.input.send(.saveButtonTapped) - dismiss(animated: true) - audioCreationCompletion?(filePath) - } } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift index 501b2cba..0d03f4fc 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift @@ -9,12 +9,13 @@ final class CreateAudioViewModel: ViewModelType { case viewDidLoad case audioButtonTapped case saveButtonTapped + case recordCancelled } enum Output { case audioFileURL(url: URL) case audioStart case audioStop - case savedAudioFile + case recordCompleted } // MARK: - Property @@ -23,6 +24,7 @@ final class CreateAudioViewModel: ViewModelType { private var audioIsRecoding: Bool = false private let completion: (Result) -> Void private let forBookID: UUID + private var fileURL: URL? // MARK: - Initializer init(forBookID: UUID, completion: @escaping (Result) -> Void) { @@ -39,7 +41,9 @@ final class CreateAudioViewModel: ViewModelType { case .audioButtonTapped: self?.audioButtonTapped() case .saveButtonTapped: - self?.saveAudioFile() + self?.completeRecord(withCompletion: true) + case .recordCancelled: + self?.completeRecord(withCompletion: false) } }.store(in: &cancellables) @@ -52,9 +56,12 @@ final class CreateAudioViewModel: ViewModelType { let url = try await MHFileManager(directoryType: .documentDirectory) .getURL(at: forBookID.uuidString, fileName: "temp.m4a") .get() + fileURL = url output.send(.audioFileURL(url: url)) } catch { MHLogger.error("Error in getting audio file url: \(error.localizedDescription)") + completion(.failure(error)) + output.send(.recordCompleted) } } private func audioButtonTapped() { @@ -67,8 +74,13 @@ final class CreateAudioViewModel: ViewModelType { audioIsRecoding.toggle() } - private func saveAudioFile() { - // TODO: - save audio file in the file system - output.send(.savedAudioFile) + private func completeRecord(withCompletion: Bool) { + if audioIsRecoding { + output.send(.audioStop) + } + output.send(.recordCompleted) + if withCompletion, let fileURL { + completion(.success(fileURL)) + } } } From f3fc4680859c6f01af02a122e68b68b33a876044 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Tue, 3 Dec 2024 22:52:37 +0900 Subject: [PATCH 20/35] WIP on feature/audioplayer --- .../UseCase/DefaultMediaUseCase.swift | 21 + .../UseCase/Interface/MediaUseCase.swift | 6 + .../View/CreateAudioViewController.swift | 1 - .../ViewModel/CreateAudioViewModel.swift | 4 + .../Audio/CreateAudioViewController.swift | 384 ------------------ .../Source/EditBook/View/EditPageCell.swift | 1 + 6 files changed, 32 insertions(+), 385 deletions(-) delete mode 100644 MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewController.swift diff --git a/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift b/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift index 0f0241ed..3578c467 100644 --- a/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift +++ b/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift @@ -65,6 +65,7 @@ public struct DefaultDeleteMediaUseCase: DeleteMediaUseCase { } public struct DefaultPersistentlyStoreMediaUseCase: PersistentlyStoreMediaUseCase { + // MARK: - Property let repository: MediaRepository @@ -84,4 +85,24 @@ public struct DefaultPersistentlyStoreMediaUseCase: PersistentlyStoreMediaUseCas try await repository.deleteMediaBySnapshot(for: bookID).get() } + + public func excute(media: MediaDescription, to bookID: UUID) async throws { + try await repository.moveTemporaryMedia(media, to: bookID).get() + } + +} + +public struct DefaultTemporaryStoreMediaUseCase: TemporaryStoreMediaUseCase { + // MARK: - Property + let repository: MediaRepository + + // MARK: - Initializer + public init(repository: MediaRepository) { + self.repository = repository + } + + // MARK: - Method + public func execute(media: MediaDescription) async throws -> URL { + try await repository.getURL(media: media, from: nil).get() + } } diff --git a/MemorialHouse/MHDomain/MHDomain/UseCase/Interface/MediaUseCase.swift b/MemorialHouse/MHDomain/MHDomain/UseCase/Interface/MediaUseCase.swift index cb434449..53615b00 100644 --- a/MemorialHouse/MHDomain/MHDomain/UseCase/Interface/MediaUseCase.swift +++ b/MemorialHouse/MHDomain/MHDomain/UseCase/Interface/MediaUseCase.swift @@ -20,4 +20,10 @@ public protocol PersistentlyStoreMediaUseCase: Sendable { /// mediaList가 없을 경우 현재 디렉토리의 스냅샷 기준으로 저장합니다. /// mediaList가 있을 경우 해당 목록을 기준으로 저장합니다. func execute(to bookID: UUID, mediaList: [MediaDescription]?) async throws + + func excute(media: MediaDescription, to bookID: UUID) async throws +} + +public protocol TemporaryStoreMediaUseCase: Sendable { + func execute(media: MediaDescription) async throws -> URL } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index ccce7d5d..087f0986 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -373,5 +373,4 @@ final class CreateAudioViewController: UIViewController { let seconds = recordingSeconds % 60 timeTextLabel.text = String(format: "%02d:%02d", minutes, seconds) } - } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift index 0d03f4fc..e1de08a0 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift @@ -2,6 +2,7 @@ import Foundation import MHData import Combine import MHCore +import MHDomain final class CreateAudioViewModel: ViewModelType { // MARK: - Type @@ -25,6 +26,7 @@ final class CreateAudioViewModel: ViewModelType { private let completion: (Result) -> Void private let forBookID: UUID private var fileURL: URL? + private var temporaryStoreMediaUsecase: TemporaryStoreMediaUseCase? // MARK: - Initializer init(forBookID: UUID, completion: @escaping (Result) -> Void) { @@ -56,6 +58,8 @@ final class CreateAudioViewModel: ViewModelType { let url = try await MHFileManager(directoryType: .documentDirectory) .getURL(at: forBookID.uuidString, fileName: "temp.m4a") .get() + + let url = try await temporaryStoreMediaUsecase?.execute(media: <#T##MediaDescription#>).get() fileURL = url output.send(.audioFileURL(url: url)) } catch { diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewController.swift deleted file mode 100644 index 5894df10..00000000 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/CreateAudioViewController.swift +++ /dev/null @@ -1,384 +0,0 @@ -import UIKit -import AVFoundation -import Combine -import MHCore - -final class CreateAudioViewController: UIViewController { - // MARK: - Property - // data bind - private var viewModel: CreateAudioViewModel? - private let input = PassthroughSubject() - private var cancellables = Set() - // auido - private var audioRecorder: AVAudioRecorder? - private var isRecording = false - // auido metering - private var upBarLayers: [CALayer] = [] - private var downBarLayers: [CALayer] = [] - private let numberOfBars = 30 - private let volumeHalfHeight: CGFloat = 40 - // timer - private var recordingSeconds: Int = 0 - private var meteringLevelTimer: Timer? - private var recordTimer: Timer? - // audio session - private let audioSession = AVAudioSession.sharedInstance() - private let audioRecordersettings: [String: Any] = [ - AVFormatIDKey: Int(kAudioFormatMPEG4AAC), - AVSampleRateKey: 44100, - AVNumberOfChannelsKey: 2, - AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue - ] - // UUID - private let identifier: UUID = UUID() - - // MARK: - UI Component - // title and buttons - private var stackView = UIStackView() - private let titleLabel: UITextField = { - let textField = UITextField( - frame: CGRect(origin: .zero, size: CGSize(width: 120, height: 28)) - ) - textField.text = "소리 기록" - textField.font = UIFont.ownglyphBerry(size: 28) - textField.textAlignment = .center - textField.textColor = .black - return textField - }() - private let cancelButton: UIButton = { - let button = UIButton( - frame: CGRect(origin: .zero, size: CGSize(width: 60, height: 21)) - ) - var attributedString = AttributedString(stringLiteral: "취소") - attributedString.font = UIFont.ownglyphBerry(size: 21) - attributedString.foregroundColor = UIColor.black - button.setAttributedTitle(NSAttributedString(attributedString), for: .normal) - return button - }() - private let saveButton: UIButton = { - let button = UIButton( - frame: CGRect(origin: .zero, size: CGSize(width: 60, height: 21)) - ) - var attributedString = AttributedString(stringLiteral: "저장") - attributedString.font = UIFont.ownglyphBerry(size: 21) - attributedString.foregroundColor = UIColor.black - button.setAttributedTitle(NSAttributedString(attributedString), for: .normal) - button.contentHorizontalAlignment = .center - return button - }() - // audio metering - private var meteringBackgroundView: UIView = UIView() - private var upMeteringView: UIView = UIView() - private var downMeteringView: UIView = UIView() - private let audioButton: UIButton = { - let button = UIButton(type: .custom) - button.backgroundColor = .red - return button - }() - private let audioButtonBackground = { - let buttonBackground = UIView() - buttonBackground.layer.borderWidth = 4 - buttonBackground.layer.borderColor = UIColor.gray.cgColor - buttonBackground.layer.cornerRadius = 30 - - return buttonBackground - }() - private var audioButtonConstraints: [NSLayoutConstraint] = [] - // timer - private var timeTextLabel: UITextField = { - let textField = UITextField() - textField.text = "00:00" - textField.textColor = .black - textField.font = UIFont.ownglyphBerry(size: 16) - - return textField - }() - - // MARK: - Initializer - init(viewModel: CreateAudioViewModel) { - self.viewModel = viewModel - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - self.viewModel = CreateAudioViewModel() - super.init(nibName: nil, bundle: nil) - } - - // MARK: - Life Cycle - override func viewDidLoad() { - super.viewDidLoad() - - setup() - bind() - configureAudioSession() - configureAddSubviews() - configureConstraints() - configureAddActions() - } - - override func viewDidDisappear(_ animated: Bool) { - self.input.send(.viewDidDisappear) - } - - // MARK: - setup - private func setup() { - view.backgroundColor = .white - setupBars() - requestMicrophonePermission() - } - - private func setupBars() { - let width = 300 / numberOfBars - 5 - let barSpacing = 5 - - for index in 0.. 2 ? -barHeight : -2 - ) - - downBarLayers[numberOfBars-1].frame = CGRect( - x: downBarLayers[numberOfBars-1].frame.origin.x, - y: 0, - width: downBarLayers[numberOfBars-1].frame.width, - height: barHeight > 2 ? barHeight : 2 - ) - } - - private func setTimeLabel(seconds recordingSeconds: Int?) { - guard let recordingSeconds = recordingSeconds else { return } - let minutes = recordingSeconds / 60 - let seconds = recordingSeconds % 60 - timeTextLabel.text = String(format: "%02d:%02d", minutes, seconds) - } - - private func configureAddActions() { - addTappedEventToAudioButton() - addTappedEventToCancelButton() - addTappedEventToSaveButton() - } - - private func addTappedEventToAudioButton() { - audioButton.addAction(UIAction { [weak self] _ in - self?.input.send(.audioButtonTapped) - }, for: .touchUpInside) - } - private func addTappedEventToCancelButton() { - cancelButton.addAction( - UIAction { [weak self]_ in - self?.dismiss(animated: true) - }, - for: .touchUpInside) - } - private func addTappedEventToSaveButton() { - saveButton.addAction(UIAction { _ in - self.input.send(.saveButtonTapped) - self.dismiss(animated: true) - }, for: .touchUpInside) - } -} diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift index 084722fb..b584e158 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift @@ -190,6 +190,7 @@ final class EditPageCell: UITableViewCell { default: MHPolaroidPhotoView() } + } private func appendAttachment(_ attachment: MediaAttachment) { guard let textStorage else { return } let text = NSMutableAttributedString(attachment: attachment) From 67670f08475cd05fcd4d4e1f9c3fb0a1c6813534 Mon Sep 17 00:00:00 2001 From: iceHood Date: Wed, 4 Dec 2024 00:03:45 +0900 Subject: [PATCH 21/35] =?UTF-8?q?feat:=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MHApplication/Source/App/SceneDelegate.swift | 15 +++++++++++++++ .../ViewModel/CreateAudioViewModelFactory.swift | 8 ++++++++ 2 files changed, 23 insertions(+) create mode 100644 MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift diff --git a/MemorialHouse/MHApplication/MHApplication/Source/App/SceneDelegate.swift b/MemorialHouse/MHApplication/MHApplication/Source/App/SceneDelegate.swift index 018b59d1..a0e3c8f8 100644 --- a/MemorialHouse/MHApplication/MHApplication/Source/App/SceneDelegate.swift +++ b/MemorialHouse/MHApplication/MHApplication/Source/App/SceneDelegate.swift @@ -233,6 +233,12 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { DeleteMediaUseCase.self, object: DefaultDeleteMediaUseCase(repository: mediaRepository) ) + + // MARK: - TemporaryStoreMedia UseCase + DIContainer.shared.register( + TemporaryStoreMediaUseCase.self, + object: DefaultTemporaryStoreMediaUseCase(repository: mediaRepository) + ) } private func registerViewModelFactoryDependency() throws { @@ -329,5 +335,14 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { ReadPageViewModelFactory.self, object: ReadPageViewModelFactory(fetchMediaUseCase: fetchMediaUseCase) ) + + // MARK: - CreateMediaViewModel + let temporaryStoreMediaUseCase = try DIContainer.shared.resolve(TemporaryStoreMediaUseCase.self) + DIContainer.shared.register( + CreateAudioViewModelFactory.self, + object: CreateAudioViewModelFactory( + temporaryStoreMediaUseCase: temporaryStoreMediaUseCase + ) + ) } } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift new file mode 100644 index 00000000..3188128e --- /dev/null +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift @@ -0,0 +1,8 @@ +// +// CreateAudioViewModelFactory.swift +// MHPresentation +// +// Created by 임정현 on 12/3/24. +// + +import Foundation From 7d8105dfb73098ed72b9030fb07ce7b51803c467 Mon Sep 17 00:00:00 2001 From: iceHood Date: Wed, 4 Dec 2024 00:04:08 +0900 Subject: [PATCH 22/35] =?UTF-8?q?feat:=20editbookVM=EC=97=90=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditBook/ViewModel/EditBookViewModel.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift index b81dc41f..03d4682d 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift @@ -9,6 +9,7 @@ final class EditBookViewModel: ViewModelType { case viewDidLoad case didAddMediaWithData(type: MediaType, data: Data) case didAddMediaInURL(type: MediaType, url: URL) + case didAddMediaInTemporary(media: MediaDescription) case addPageButtonTapped case didSaveButtonTapped case didCancelButtonTapped @@ -27,7 +28,7 @@ final class EditBookViewModel: ViewModelType { private let createMediaUseCase: CreateMediaUseCase private let fetchMediaUseCase: FetchMediaUseCase private let deleteMediaUseCase: DeleteMediaUseCase - let bookID: UUID + private let bookID: UUID private var title: String = "" private var editPageViewModels: [EditPageViewModel] = [] private var currentPageIndex = 0 @@ -61,6 +62,8 @@ final class EditBookViewModel: ViewModelType { Task { await self?.addMedia(type: type, with: data) } case let .didAddMediaInURL(type, url): Task { await self?.addMedia(type: type, in: url) } + case let .didAddMediaInTemporary(media): + Task { await self?.addMedia(media) } case .addPageButtonTapped: self?.addEmptyPage() case .didSaveButtonTapped: @@ -118,6 +121,16 @@ final class EditBookViewModel: ViewModelType { MHLogger.error(error.localizedDescription + #function) } } + private func addMedia(_ description: MediaDescription) async { + do { + try await storeMediaUseCase.excute(media: description, to: bookID) + let url: URL = try await fetchMediaUseCase.execute(media: description, in: bookID) + editPageViewModels[currentPageIndex].addMedia(media: description, url: url) + } catch { + output.send(.error(message: "미디어를 추가하는데 실패했습니다.")) + MHLogger.error(error.localizedDescription + #function) + } + } private func addEmptyPage() { let page = Page(id: UUID(), metadata: [:], text: "") let editPageViewModel = EditPageViewModel( From 004f6e3066c14a1395791b9991ff909ce931b20c Mon Sep 17 00:00:00 2001 From: iceHood Date: Wed, 4 Dec 2024 00:04:25 +0900 Subject: [PATCH 23/35] =?UTF-8?q?feat:=20audioViewModelFactory=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditBook/View/EditBookViewController.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift index f51a3222..cfcac8e5 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditBookViewController.swift @@ -229,15 +229,11 @@ final class EditBookViewController: UIViewController { let addAudioAction = UIAction { [weak self] _ in guard let self else { return } - let audioViewModel = CreateAudioViewModel( - forBookID: viewModel.bookID) { [weak self] result in - switch result { - case .success(let url): - self?.input.send(.didAddMediaInURL(type: .audio, url: url)) - case .failure(let failure): - return - } - } + guard let audioViewModelFactory = try? DIContainer.shared.resolve(CreateAudioViewModelFactory.self) else { return } + let audioViewModel = audioViewModelFactory.make { [weak self] mediaDescription in + guard let mediaDescription else { return } + self?.input.send(.didAddMediaInTemporary(media: mediaDescription)) + } let audioViewController = CreateAudioViewController(viewModel: audioViewModel) if let sheet = audioViewController.sheetPresentationController { From 3200219edca957faa7eb122f898be32d1d1c7e7f Mon Sep 17 00:00:00 2001 From: iceHood Date: Wed, 4 Dec 2024 00:04:40 +0900 Subject: [PATCH 24/35] =?UTF-8?q?feat:=20audioViewModelFactory=20=EB=A7=8C?= =?UTF-8?q?=EB=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateAudioViewModelFactory.swift | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift index 3188128e..0e6c6f16 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModelFactory.swift @@ -1,8 +1,18 @@ -// -// CreateAudioViewModelFactory.swift -// MHPresentation -// -// Created by 임정현 on 12/3/24. -// +import MHFoundation +import MHDomain + +public struct CreateAudioViewModelFactory { + private let temporaryStoreMediaUseCase: TemporaryStoreMediaUseCase + + public init(temporaryStoreMediaUseCase: TemporaryStoreMediaUseCase) { + self.temporaryStoreMediaUseCase = temporaryStoreMediaUseCase + } + + public func make(completion: @escaping (MediaDescription?) -> Void) -> CreateAudioViewModel { + CreateAudioViewModel( + temporaryStoreMediaUsecase: temporaryStoreMediaUseCase, + completion: completion + ) + } +} -import Foundation From ebba5de2a1f354f2f63fa82d9fe2f75c13f54c08 Mon Sep 17 00:00:00 2001 From: iceHood Date: Wed, 4 Dec 2024 00:05:13 +0900 Subject: [PATCH 25/35] =?UTF-8?q?feat:=20createAudio=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CreateAudioViewController.swift | 5 ++- .../ViewModel/CreateAudioViewModel.swift | 34 ++++++++----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 087f0986..9c7b1d7d 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -98,7 +98,8 @@ final class CreateAudioViewController: UIViewController { } required init?(coder: NSCoder) { - self.viewModel = CreateAudioViewModel(forBookID: .init(), completion: { _ in }) + guard let viewModelFactory = try? DIContainer.shared.resolve(CreateAudioViewModelFactory.self) else { return nil } + self.viewModel = viewModelFactory.make { _ in } super.init(nibName: nil, bundle: nil) } @@ -111,6 +112,7 @@ final class CreateAudioViewController: UIViewController { configureAddSubviews() configureConstraints() configureAddActions() + input.send(.viewDidLoad) } // MARK: - Setup @@ -173,6 +175,7 @@ final class CreateAudioViewController: UIViewController { audioRecorder = try? AVAudioRecorder(url: url, settings: audioRecordersettings) audioRecorder?.isMeteringEnabled = true + print("URL: \(url)") } private func configureAddSubviews() { diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift index e1de08a0..d392feac 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/ViewModel/CreateAudioViewModel.swift @@ -1,10 +1,9 @@ import Foundation -import MHData import Combine import MHCore import MHDomain -final class CreateAudioViewModel: ViewModelType { +public final class CreateAudioViewModel: ViewModelType { // MARK: - Type enum Input { case viewDidLoad @@ -23,14 +22,16 @@ final class CreateAudioViewModel: ViewModelType { private let output = PassthroughSubject() private var cancellables = Set() private var audioIsRecoding: Bool = false - private let completion: (Result) -> Void - private let forBookID: UUID - private var fileURL: URL? - private var temporaryStoreMediaUsecase: TemporaryStoreMediaUseCase? + private let completion: (MediaDescription?) -> Void + private let temporaryStoreMediaUsecase: TemporaryStoreMediaUseCase + private var mediaDescription: MediaDescription? // MARK: - Initializer - init(forBookID: UUID, completion: @escaping (Result) -> Void) { - self.forBookID = forBookID + init( + temporaryStoreMediaUsecase: TemporaryStoreMediaUseCase, + completion: @escaping (MediaDescription?) -> Void + ) { + self.temporaryStoreMediaUsecase = temporaryStoreMediaUsecase self.completion = completion } @@ -54,17 +55,14 @@ final class CreateAudioViewModel: ViewModelType { // MARK: - Helper private func viewDidLoad() async { + let mediaDescription = MediaDescription(type: .audio) + self.mediaDescription = mediaDescription do { - let url = try await MHFileManager(directoryType: .documentDirectory) - .getURL(at: forBookID.uuidString, fileName: "temp.m4a") - .get() - - let url = try await temporaryStoreMediaUsecase?.execute(media: <#T##MediaDescription#>).get() - fileURL = url + let url = try await temporaryStoreMediaUsecase.execute(media: mediaDescription) output.send(.audioFileURL(url: url)) } catch { - MHLogger.error("Error in getting audio file url: \(error.localizedDescription)") - completion(.failure(error)) + MHLogger.error("Error in store audio file url: \(error.localizedDescription)") + completion(nil) output.send(.recordCompleted) } } @@ -83,8 +81,6 @@ final class CreateAudioViewModel: ViewModelType { output.send(.audioStop) } output.send(.recordCompleted) - if withCompletion, let fileURL { - completion(.success(fileURL)) - } + completion(mediaDescription) } } From 2f5c8cc4e1f8c43a318ec6ffe3d29a5396dc970c Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 02:21:10 +0900 Subject: [PATCH 26/35] feat: wip --- .../LocalStorage/FileManager/MHFileManager.swift | 16 ++++++++++++++++ .../MHData/MHData/LocalStorage/FileStorage.swift | 2 ++ .../MHData/Repository/LocalMediaRepository.swift | 10 +++++++--- .../MHDomain/Repository/MediaRepository.swift | 1 + .../MHDomain/UseCase/DefaultMediaUseCase.swift | 3 ++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/MemorialHouse/MHData/MHData/LocalStorage/FileManager/MHFileManager.swift b/MemorialHouse/MHData/MHData/LocalStorage/FileManager/MHFileManager.swift index f2ab0171..ae1a642f 100644 --- a/MemorialHouse/MHData/MHData/LocalStorage/FileManager/MHFileManager.swift +++ b/MemorialHouse/MHData/MHData/LocalStorage/FileManager/MHFileManager.swift @@ -193,6 +193,22 @@ extension MHFileManager: FileStorage { return .success(originDataPath) } + public func makeDirectory(through path: String) async -> Result { + guard let originDirectory = fileManager.urls( + for: directoryType, + in: .userDomainMask + ).first?.appending(path: path) + else { return .failure(.directorySettingFailure) } + guard ( + try? fileManager.createDirectory( + at: originDirectory, + withIntermediateDirectories: true + ) + ) != nil else { return .failure(.directorySettingFailure)} + + return .success(()) + } + public func getFileNames(at path: String) async -> Result<[String], MHDataError> { guard let originDirectory = fileManager.urls( for: directoryType, diff --git a/MemorialHouse/MHData/MHData/LocalStorage/FileStorage.swift b/MemorialHouse/MHData/MHData/LocalStorage/FileStorage.swift index dd981155..e6a9087f 100644 --- a/MemorialHouse/MHData/MHData/LocalStorage/FileStorage.swift +++ b/MemorialHouse/MHData/MHData/LocalStorage/FileStorage.swift @@ -70,6 +70,8 @@ public protocol FileStorage: Sendable { /// - Returns: 파일 URL을 반환합니다. func getURL(at path: String, fileName name: String) async -> Result + func makeDirectory(through path: String) async -> Result + /// 지정된 경로의 파일 목록을 반환합니다. /// Documents폴더를 기준으로 파일 이름 목록을 반환합니다. /// path는 디렉토리여야 합니다. diff --git a/MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift b/MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift index efc45fb4..b9266dcf 100644 --- a/MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift +++ b/MemorialHouse/MHData/MHData/Repository/LocalMediaRepository.swift @@ -7,7 +7,7 @@ import AVFoundation // TODO: nil이라면 바로 error를 return하도록 수정 public struct LocalMediaRepository: MediaRepository, Sendable { private let storage: FileStorage - private let temporaryPath = "temp" // TODO: - 지워질 것임! + private let temporaryPath = "temporary" private let snapshotFileName = ".snapshot" public init(storage: FileStorage) { @@ -68,9 +68,9 @@ public struct LocalMediaRepository: MediaRepository, Sendable { to bookID: UUID ) async -> Result { let path = bookID.uuidString - let fileName = mediaDescription.id.uuidString + let fileName = fileName(of: mediaDescription) - return await storage.move(at: "temp", fileName: fileName, to: path) + return await storage.move(at: temporaryPath, fileName: fileName, to: path) } public func getURL( @@ -85,6 +85,10 @@ public struct LocalMediaRepository: MediaRepository, Sendable { return await storage.getURL(at: path, fileName: fileName) } + public func makeTemporaryDirectory() async -> Result { + return await storage.makeDirectory(through: temporaryPath) + } + public func moveAllTemporaryMedia(to bookID: UUID) async -> Result { let path = bookID.uuidString diff --git a/MemorialHouse/MHDomain/MHDomain/Repository/MediaRepository.swift b/MemorialHouse/MHDomain/MHDomain/Repository/MediaRepository.swift index ca64badd..5dccad0a 100644 --- a/MemorialHouse/MHDomain/MHDomain/Repository/MediaRepository.swift +++ b/MemorialHouse/MHDomain/MHDomain/Repository/MediaRepository.swift @@ -6,6 +6,7 @@ public protocol MediaRepository: Sendable { func create(media mediaDescription: MediaDescription, from: URL, to bookID: UUID?) async -> Result func fetch(media mediaDescription: MediaDescription, from bookID: UUID?) async -> Result func getURL(media mediaDescription: MediaDescription, from bookID: UUID?) async -> Result + func makeTemporaryDirectory() async -> Result func delete(media mediaDescription: MediaDescription, at bookID: UUID?) async -> Result func moveTemporaryMedia(_ mediaDescription: MediaDescription, to bookID: UUID) async -> Result func moveAllTemporaryMedia(to bookID: UUID) async -> Result diff --git a/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift b/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift index 3578c467..597d2f61 100644 --- a/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift +++ b/MemorialHouse/MHDomain/MHDomain/UseCase/DefaultMediaUseCase.swift @@ -103,6 +103,7 @@ public struct DefaultTemporaryStoreMediaUseCase: TemporaryStoreMediaUseCase { // MARK: - Method public func execute(media: MediaDescription) async throws -> URL { - try await repository.getURL(media: media, from: nil).get() + try await repository.makeTemporaryDirectory().get() + return try await repository.getURL(media: media, from: nil).get() } } From 844e0f8eaecb319e2f2a1fc071027c6e1f2459bd Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 02:48:29 +0900 Subject: [PATCH 27/35] feat: read audio --- .../Source/EditBook/View/EditPageCell.swift | 13 ++++++++----- .../EditBook/ViewModel/EditBookViewModel.swift | 6 ------ .../ReadPage/View/ReadPageViewController.swift | 10 +++++++++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift index 381d5765..92ad2316 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift @@ -144,6 +144,12 @@ final class EditPageCell: UITableViewCell { description: description ) input.send(.didRequestMediaDataForURL(media: description)) + case .audio: + mediaAttachment = MediaAttachment( + view: MHAudioPlayerView(), + description: description + ) + input.send(.didRequestMediaDataForURL(media: description)) default: break } @@ -180,9 +186,8 @@ final class EditPageCell: UITableViewCell { description: media ) case .audio: - // TODO: - audio 추가 필요 attachment = MediaAttachment( - view: MHPolaroidPhotoView(), + view: MHAudioPlayerView(), description: media ) default: @@ -194,7 +199,6 @@ final class EditPageCell: UITableViewCell { } private func mediaAddedWithURL(media: MediaDescription, url: URL) { - var attachment: MediaAttachment? switch media.type { case .image: @@ -208,9 +212,8 @@ final class EditPageCell: UITableViewCell { description: media ) case .audio: - // TODO: - audio 추가 필요 attachment = MediaAttachment( - view: MHPolaroidPhotoView(), + view: MHAudioPlayerView(), description: media ) default: diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift index 25f1d0d2..ed8d9141 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/ViewModel/EditBookViewModel.swift @@ -7,8 +7,6 @@ final class EditBookViewModel: ViewModelType { // MARK: - Type enum Input { case viewDidLoad - case didAddMediaWithData(type: MediaType, data: Data) - case didAddMediaInURL(type: MediaType, url: URL) case didAddMediaInTemporary(media: MediaDescription) case didAddMediaWithData(type: MediaType, attributes: [String: any Sendable]?, data: Data) case didAddMediaInURL(type: MediaType, attributes: [String: any Sendable]?, url: URL) @@ -62,10 +60,6 @@ final class EditBookViewModel: ViewModelType { switch event { case .viewDidLoad: Task { await self?.fetchBook() } - case let .didAddMediaWithData(type, data): - Task { await self?.addMedia(type: type, with: data) } - case let .didAddMediaInURL(type, url): - Task { await self?.addMedia(type: type, in: url) } case let .didAddMediaInTemporary(media): Task { await self?.addMedia(media) } case let .didAddMediaWithData(type, attributes, data): diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/ReadPage/View/ReadPageViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/ReadPage/View/ReadPageViewController.swift index df521286..395771bf 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/ReadPage/View/ReadPageViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/ReadPage/View/ReadPageViewController.swift @@ -105,7 +105,9 @@ final class ReadPageViewController: UIViewController { attachmentMetaData: [Int: MediaDescription] ) -> NSAttributedString { let mutableAttributedString = NSMutableAttributedString(string: text) - attachmentMetaData.forEach { location, description in + attachmentMetaData.forEach { + location, + description in // TODO: - MediaType 별로 바꿔줘야함 var mediaAttachment: MediaAttachment? switch description.type { @@ -121,6 +123,12 @@ final class ReadPageViewController: UIViewController { description: description ) input.send(.didRequestMediaDataForURL(media: description)) + case .audio: + mediaAttachment = MediaAttachment( + view: MHAudioPlayerView(), + description: description + ) + input.send(.didRequestMediaDataForURL(media: description)) default: break } From 047311f0012fe7c233932f3dbe65f72333732135 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 03:26:44 +0900 Subject: [PATCH 28/35] chore: apply review --- .../MHApplication/Resource/Info.plist | 2 -- .../Audio/Player/View/MHAudioPlayerView.swift | 31 +++++++++---------- .../Source/EditBook/View/EditPageCell.swift | 2 +- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist index abd27cfb..a3c8cdd2 100644 --- a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist +++ b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist @@ -4,8 +4,6 @@ UIFileSharingEnabled - LSSupportsOpeningDocumentsInPlace - UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index c6fe4cbe..34a33ffa 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -4,16 +4,15 @@ import AVFAudio import MHCore import MHDomain - -final public class MHAudioPlayerView: UIView { +final class MHAudioPlayerView: UIView { // MARK: - Property // data bind private var viewModel: AudioPlayerViewModel? private let input = PassthroughSubject() private var cancellables = Set() // audio - nonisolated(unsafe) var audioPlayer: AVAudioPlayer? - var audioPlayState: AudioPlayState = .pause { + private nonisolated(unsafe) var audioPlayer: AVAudioPlayer? + private var audioPlayState: AudioPlayState = .pause { didSet { switch audioPlayState { case .play: @@ -23,10 +22,10 @@ final public class MHAudioPlayerView: UIView { } } } - var timer: Timer? + private var timer: Timer? // MARK: - ViewComponent - let backgroundBorderView: UIView = { + private let backgroundBorderView: UIView = { let backgroundBorderView = UIView() backgroundBorderView.backgroundColor = .baseBackground backgroundBorderView.layer.borderWidth = 3 @@ -35,31 +34,29 @@ final public class MHAudioPlayerView: UIView { return backgroundBorderView }() - let audioProgressView: UIView = { + private let audioProgressView: UIView = { let backgroundView = UIView() backgroundView.backgroundColor = .mhPink -// backgroundView.layer.borderWidth = 4 backgroundView.layer.cornerRadius = 21 -// backgroundView.layer.borderColor = UIColor.baseBackground.cgColor return backgroundView }() - var progressViewWidthConstraint: NSLayoutConstraint? - var progressViewConstraints: [NSLayoutConstraint] = [] - let audioStateButton: UIButton = { + private var progressViewWidthConstraint: NSLayoutConstraint? + private var progressViewConstraints: [NSLayoutConstraint] = [] + private let audioStateButton: UIButton = { let button = UIButton() button.setImage(UIImage(systemName: "play.fill"), for: .normal) return button }() - let playImage = UIImage(systemName: "play.fill") - let pauseImage = UIImage(systemName: "pause.fill") - let audioStateImageView: UIImageView = { + private let playImage = UIImage(systemName: "play.fill") + private let pauseImage = UIImage(systemName: "pause.fill") + private let audioStateImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "audio_play") return imageView }() - let audioPlayTimeLabel: UILabel = { + private let audioPlayTimeLabel: UILabel = { let label = UILabel() label.text = "00:00" label.font = UIFont.ownglyphBerry(size: 21) @@ -207,7 +204,7 @@ final public class MHAudioPlayerView: UIView { } extension MHAudioPlayerView: AVAudioPlayerDelegate { - nonisolated public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + nonisolated func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { MHLogger.debug("audio player finished") Task { @MainActor in diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift index 92ad2316..c1a4c970 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift @@ -256,7 +256,7 @@ final class EditPageCell: UITableViewCell { case .image: MHPolaroidPhotoView() case .video: - MHPolaroidPhotoView() + MHVideoView() case .audio: MHAudioPlayerView() default: From a01cc4755ab5893e3050347d45bad3e5958588f7 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 04:21:17 +0900 Subject: [PATCH 29/35] chore: apply review --- .../MHApplication/Resource/Info.plist | 2 -- .../View/CreateAudioViewController.swift | 5 +++-- .../Audio/Player/View/MHAudioPlayerView.swift | 20 +++---------------- .../Source/EditBook/View/EditPageCell.swift | 12 ----------- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist index a3c8cdd2..0eb786dc 100644 --- a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist +++ b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist @@ -2,8 +2,6 @@ - UIFileSharingEnabled - UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 9c7b1d7d..9a7d4dd1 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -175,7 +175,6 @@ final class CreateAudioViewController: UIViewController { audioRecorder = try? AVAudioRecorder(url: url, settings: audioRecordersettings) audioRecorder?.isMeteringEnabled = true - print("URL: \(url)") } private func configureAddSubviews() { @@ -244,6 +243,7 @@ final class CreateAudioViewController: UIViewController { self?.input.send(.audioButtonTapped) }, for: .touchUpInside) } + private func addTappedEventToCancelButton() { cancelButton.addAction( UIAction { [weak self] _ in @@ -251,6 +251,7 @@ final class CreateAudioViewController: UIViewController { }, for: .touchUpInside) } + private func addTappedEventToSaveButton() { saveButton.addAction( UIAction { [weak self] _ in @@ -272,7 +273,7 @@ final class CreateAudioViewController: UIViewController { self.present(alert, animated: true, completion: nil) } } else { - AVAudioSession.sharedInstance().requestRecordPermission { granted in + AVAudioSession.sharedInstance().requestRecordPermission { @Sendable granted in Task { @MainActor in if !granted { self.present(alert, animated: true, completion: nil) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index 34a33ffa..ad4a4fa6 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -150,17 +150,12 @@ final class MHAudioPlayerView: UIView { private func updateAudioPlayImage(audioPlayState state: AudioPlayState) { switch state { case .play: - MHLogger.debug("pause") audioStateButton.setImage(playImage, for: .normal) audioPlayer?.pause() audioPlayState = .pause case .pause: audioStateButton.setImage(pauseImage, for: .normal) - if audioPlayer?.play() == false { - MHLogger.error("do not play") - } else { - MHLogger.debug("do play") - } + audioPlayer?.play() audioPlayState = .play } } @@ -170,9 +165,7 @@ final class MHAudioPlayerView: UIView { let width = ceil(Float(audioPlayer.currentTime) / Float(audioPlayer.duration) * Float(299)) progressViewWidthConstraint?.constant = CGFloat(width) - UIView.animate(withDuration: 0) { - self.layoutIfNeeded() - } + self.layoutIfNeeded() } private func startTimer() { @@ -183,8 +176,6 @@ final class MHAudioPlayerView: UIView { if audioPlayer.isPlaying { self?.updatePlayAudioProgress() self?.setTimeLabel(seconds: Int(audioPlayer.currentTime)) - } else { - } } } @@ -205,8 +196,6 @@ final class MHAudioPlayerView: UIView { extension MHAudioPlayerView: AVAudioPlayerDelegate { nonisolated func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - MHLogger.debug("audio player finished") - Task { @MainActor in self.audioPlayState = .pause self.audioStateButton.setImage(playImage, for: .normal) @@ -222,12 +211,9 @@ extension MHAudioPlayerView: AVAudioPlayerDelegate { } extension MHAudioPlayerView: @preconcurrency MediaAttachable { - func configureSource(with mediaDescription: MediaDescription, data: Data) { - - } + func configureSource(with mediaDescription: MediaDescription, data: Data) { } func configureSource(with mediaDescription: MediaDescription, url: URL) { - MHLogger.debug("configure source \(url)") audioPlayer = try? AVAudioPlayer(contentsOf: url) guard let audioPlayer else { return } audioPlayer.delegate = self diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift index c1a4c970..1588f70f 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditBook/View/EditPageCell.swift @@ -250,18 +250,6 @@ final class EditPageCell: UITableViewCell { attachment = mediaAttachment } return attachment - } - private func mediaViewFactory(type: MediaType) -> UIView & MediaAttachable { - switch type { - case .image: - MHPolaroidPhotoView() - case .video: - MHVideoView() - case .audio: - MHAudioPlayerView() - default: - MHPolaroidPhotoView() - } } private func appendAttachment(_ attachment: MediaAttachment) { From 3fa7a1aa4d128c739548e9b1c1027b0dd947dacc Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 04:38:53 +0900 Subject: [PATCH 30/35] chore: apply review --- .../Audio/View/CreateAudioViewController.swift | 18 +++++++----------- .../Audio/Player/View/MHAudioPlayerView.swift | 7 ++++++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 9a7d4dd1..189aa8a6 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -266,18 +266,14 @@ final class CreateAudioViewController: UIViewController { message: "설정에서 마이크 권한을 허용해주세요.", preferredStyle: .alert ) - alert.addAction(UIAlertAction(title: "OK", style: .default)) + alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in + self?.dismiss(animated: true) + }) Task { - if #available(iOS 17, *) { - if await !AVAudioApplication.requestRecordPermission() { - self.present(alert, animated: true, completion: nil) - } - } else { - AVAudioSession.sharedInstance().requestRecordPermission { @Sendable granted in - Task { @MainActor in - if !granted { - self.present(alert, animated: true, completion: nil) - } + AVAudioSession.sharedInstance().requestRecordPermission { @Sendable granted in + Task { @MainActor in + if !granted { + self.present(alert, animated: true, completion: nil) } } } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index ad4a4fa6..cbf0a7dc 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -211,7 +211,12 @@ extension MHAudioPlayerView: AVAudioPlayerDelegate { } extension MHAudioPlayerView: @preconcurrency MediaAttachable { - func configureSource(with mediaDescription: MediaDescription, data: Data) { } + func configureSource(with mediaDescription: MediaDescription, data: Data) { + audioPlayer = try? AVAudioPlayer(data: data) + guard let audioPlayer else { return } + audioPlayer.delegate = self + self.setTimeLabel(seconds: Int(audioPlayer.duration.rounded())) + } func configureSource(with mediaDescription: MediaDescription, url: URL) { audioPlayer = try? AVAudioPlayer(contentsOf: url) From ee798d1c2dd6f03a4570c81be99d395188fc52e2 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 04:39:19 +0900 Subject: [PATCH 31/35] feat: fix light mode --- MemorialHouse/MHApplication/MHApplication/Resource/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist index 0eb786dc..ef1c739d 100644 --- a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist +++ b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist @@ -2,6 +2,8 @@ + UIUserInterfaceStyle + Light UIApplicationSceneManifest UIApplicationSupportsMultipleScenes From aec03b9b2188b818b127be55e8e9847f95510648 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 04:43:55 +0900 Subject: [PATCH 32/35] chore: apply review --- .../Source/Audio/Audio/View/CreateAudioViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 189aa8a6..945b6a2d 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -248,8 +248,7 @@ final class CreateAudioViewController: UIViewController { cancelButton.addAction( UIAction { [weak self] _ in self?.input.send(.recordCancelled) - }, - for: .touchUpInside) + }, for: .touchUpInside) } private func addTappedEventToSaveButton() { From 3ee5c61d4c9b95e4332a412563a24a22e33f4bcb Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 04:46:38 +0900 Subject: [PATCH 33/35] chore: apply review --- .../Audio/View/CreateAudioViewController.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift index 945b6a2d..c7b7789e 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Audio/View/CreateAudioViewController.swift @@ -239,23 +239,27 @@ final class CreateAudioViewController: UIViewController { } private func addTappedEventToAudioButton() { - audioButton.addAction(UIAction { [weak self] _ in - self?.input.send(.audioButtonTapped) - }, for: .touchUpInside) + audioButton.addAction( + UIAction { [weak self] _ in + self?.input.send(.audioButtonTapped) + }, for: .touchUpInside + ) } private func addTappedEventToCancelButton() { cancelButton.addAction( UIAction { [weak self] _ in self?.input.send(.recordCancelled) - }, for: .touchUpInside) + }, for: .touchUpInside + ) } private func addTappedEventToSaveButton() { saveButton.addAction( UIAction { [weak self] _ in self?.input.send(.saveButtonTapped) - }, for: .touchUpInside) + }, for: .touchUpInside + ) } // MARK: - Helper From 74d51653d2c323cf44eea219e0b620e7dafcfa9f Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 09:40:01 +0900 Subject: [PATCH 34/35] chore: delete unused code --- .../Source/Audio/Player/View/MHAudioPlayerView.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift index cbf0a7dc..dfed73de 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Audio/Player/View/MHAudioPlayerView.swift @@ -50,12 +50,6 @@ final class MHAudioPlayerView: UIView { }() private let playImage = UIImage(systemName: "play.fill") private let pauseImage = UIImage(systemName: "pause.fill") - private let audioStateImageView: UIImageView = { - let imageView = UIImageView() - imageView.image = UIImage(named: "audio_play") - - return imageView - }() private let audioPlayTimeLabel: UILabel = { let label = UILabel() label.text = "00:00" From 9239faf193f8aaa0aeba1c99fe4ec299a45a0d75 Mon Sep 17 00:00:00 2001 From: yuncheol-AHN Date: Wed, 4 Dec 2024 09:57:32 +0900 Subject: [PATCH 35/35] chore: fix portrait --- .../MHApplication.xcodeproj/project.pbxproj | 10 ++++++---- .../MHApplication/MHApplication/Resource/Info.plist | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/MemorialHouse/MHApplication/MHApplication.xcodeproj/project.pbxproj b/MemorialHouse/MHApplication/MHApplication.xcodeproj/project.pbxproj index 0cd07868..10ac92b6 100644 --- a/MemorialHouse/MHApplication/MHApplication.xcodeproj/project.pbxproj +++ b/MemorialHouse/MHApplication/MHApplication.xcodeproj/project.pbxproj @@ -235,8 +235,9 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "기록소는 사진 권한을 필요로 합니다. 허용 안 함 시 일부 기능이 동작하지 않을 수 있습니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -272,8 +273,9 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "기록소는 사진 권한을 필요로 합니다. 허용 안 함 시 일부 기능이 동작하지 않을 수 있습니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist index ef1c739d..4b6ec74d 100644 --- a/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist +++ b/MemorialHouse/MHApplication/MHApplication/Resource/Info.plist @@ -2,8 +2,8 @@ - UIUserInterfaceStyle - Light + UIUserInterfaceStyle + Light UIApplicationSceneManifest UIApplicationSupportsMultipleScenes