diff --git a/iOS/Layover/Layover/DesignSystem/LOImageLabel.swift b/iOS/Layover/Layover/DesignSystem/LOImageLabel.swift index f30387f..30e6556 100644 --- a/iOS/Layover/Layover/DesignSystem/LOImageLabel.swift +++ b/iOS/Layover/Layover/DesignSystem/LOImageLabel.swift @@ -20,6 +20,7 @@ final class LOImageLabel: UIView { let label = UILabel() label.font = .loFont(type: .body2Semibold) label.textAlignment = .left + label.numberOfLines = 1 return label }() @@ -54,6 +55,7 @@ final class LOImageLabel: UIView { titleLabel.topAnchor.constraint(equalTo: topAnchor), titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor), titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } diff --git a/iOS/Layover/Layover/Extensions/Notification.Name+.swift b/iOS/Layover/Layover/Extensions/Notification.Name+.swift index ebb5e37..55386f2 100644 --- a/iOS/Layover/Layover/Extensions/Notification.Name+.swift +++ b/iOS/Layover/Layover/Extensions/Notification.Name+.swift @@ -13,4 +13,5 @@ extension Notification.Name { static let uploadTaskStart = Notification.Name("uploadTaskStart") static let progressChanged = Notification.Name("progressChanged") static let uploadTaskDidComplete = Notification.Name("uploadTaskDidComplete") + static let uploadTaskDidFail = Notification.Name("uploadTaskDidFail") } diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index 89bf2d1..8247158 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -6,6 +6,8 @@ // import Foundation +import UniformTypeIdentifiers + import OSLog protocol ProviderType { @@ -40,15 +42,15 @@ extension ProviderType { } func upload(fromFile: URL, - to url: String, - method: HTTPMethod = .PUT, - sessionTaskDelegate: URLSessionTaskDelegate? = nil, - delegateQueue: OperationQueue? = nil) async throws -> Data { + to url: String, + method: HTTPMethod = .PUT, + sessionTaskDelegate: URLSessionTaskDelegate? = nil, + delegateQueue: OperationQueue? = nil) async throws -> Data { return try await upload(fromFile: fromFile, - to: url, - method: method, - sessionTaskDelegate: sessionTaskDelegate, - delegateQueue: delegateQueue) + to: url, + method: method, + sessionTaskDelegate: sessionTaskDelegate, + delegateQueue: delegateQueue) } } @@ -159,9 +161,10 @@ class Provider: ProviderType { sessionTaskDelegate: URLSessionTaskDelegate? = nil, delegateQueue: OperationQueue? = nil) async throws -> Data { guard let url = URL(string: url) else { throw NetworkError.components } + guard let mimeType = UTType(filenameExtension: fromFile.pathExtension)?.preferredMIMEType else { throw NetworkError.unknown } var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) request.httpMethod = method.rawValue - request.setValue("video/\(fromFile.pathExtension)", forHTTPHeaderField: "Content-Type") + request.setValue(mimeType, forHTTPHeaderField: "Content-Type") let (data, response) = try await session.upload(for: request, fromFile: fromFile, delegate: sessionTaskDelegate) diff --git a/iOS/Layover/Layover/SceneDelegate.swift b/iOS/Layover/Layover/SceneDelegate.swift index 3f219ee..1e9df84 100644 --- a/iOS/Layover/Layover/SceneDelegate.swift +++ b/iOS/Layover/Layover/SceneDelegate.swift @@ -81,10 +81,14 @@ extension SceneDelegate { self?.showProgressView() } NotificationCenter.default.addObserver(forName: .progressChanged, object: nil, queue: .main) { [weak self] notification in - self?.progressChanged(notification) + guard let progress = notification.userInfo?["progress"] as? Float else { return } + self?.progressView.setProgress(progress, animated: true) } NotificationCenter.default.addObserver(forName: .uploadTaskDidComplete, object: nil, queue: .main) { [weak self] _ in - self?.removeProgressView() + self?.removeProgressView(message: "업로드가 완료되었습니다 ✨") + } + NotificationCenter.default.addObserver(forName: .uploadTaskDidFail, object: nil, queue: .main) { [weak self] _ in + self?.removeProgressView(message: "업로드에 실패했습니다 💦") } } @@ -93,7 +97,7 @@ extension SceneDelegate { name: .refreshTokenDidExpired, object: nil) NotificationCenter.default.removeObserver(self, - name: .refreshTokenDidExpired, + name: .uploadTaskStart, object: nil) NotificationCenter.default.removeObserver(self, name: .progressChanged, @@ -101,6 +105,9 @@ extension SceneDelegate { NotificationCenter.default.removeObserver(self, name: .uploadTaskDidComplete, object: nil) + NotificationCenter.default.removeObserver(self, + name: .uploadTaskDidFail, + object: nil) } @objc private func routeToLoginViewController() { @@ -113,8 +120,10 @@ extension SceneDelegate { private func showProgressView() { guard let progressViewWidth = window?.screen.bounds.width, let windowHeight = window?.screen.bounds.height, - let tabBarViewController = window?.rootViewController as? UITabBarController else { return } - let tabBarHeight: CGFloat = tabBarViewController.tabBar.frame.height + let navigationController = window?.rootViewController as? UINavigationController, + let tabBarController = navigationController.topViewController as? UITabBarController + else { return } + let tabBarHeight: CGFloat = tabBarController.tabBar.frame.height progressView.progress = 0 progressView.tintColor = .primaryPurple progressView.frame = CGRect(x: 0, @@ -124,17 +133,9 @@ extension SceneDelegate { window?.addSubview(progressView) } - - private func progressChanged(_ notification: Notification) { - guard let progress = notification.userInfo?["progress"] as? Float else { return } - progressView.setProgress(progress, animated: true) - if progress == 1 { - Toast.shared.showToast(message: "업로드가 완료되었습니다 ✨") - } - } - - private func removeProgressView() { + private func removeProgressView(message: String) { progressView.removeFromSuperview() + Toast.shared.showToast(message: message) } } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift index 0709dab..ff7f2c8 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift @@ -36,8 +36,11 @@ final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { func fetchVideo(request: EditVideoModels.FetchVideo.Request) { let isEdited = request.editedVideoURL != nil - guard let videoURL = isEdited ? request.editedVideoURL : videoURL else { return } + if let editedVideoURL = request.editedVideoURL { + videoURL = editedVideoURL + } + guard let videoURL else { return } Task { let duration = try await AVAsset(url: videoURL).load(.duration) let seconds = CMTimeGetSeconds(duration) diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index e62532c..f615178 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -114,6 +114,7 @@ final class EditVideoViewController: BaseViewController { } @objc private func cutButtonDidTap() { + loopingPlayerView.player?.pause() guard let videoPath = originalVideoURL?.path() else { return } if UIVideoEditorController.canEditVideo(atPath: videoPath) { let editController = UIVideoEditorController() @@ -139,7 +140,24 @@ extension EditVideoViewController: UINavigationControllerDelegate, UIVideoEditor func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath editedVideoPath: String) { let editedVideoURL = NSURL(fileURLWithPath: editedVideoPath) as URL interactor?.fetchVideo(request: EditVideoModels.FetchVideo.Request(editedVideoURL: editedVideoURL)) - dismiss(animated: true) + editor.dismiss(animated: true) { [weak self] in + guard let self else { return } + self.loopingPlayerView.play() + } + } + + func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) { + editor.dismiss(animated: true) { [weak self] in + guard let self else { return } + self.loopingPlayerView.play() + } + } + + func videoEditorController(_ editor: UIVideoEditorController, didFailWithError error: Error) { + editor.dismiss(animated: true) { [weak self] in + guard let self else { return } + self.loopingPlayerView.play() + } } } @@ -155,7 +173,12 @@ extension EditVideoViewController: EditVideoDisplayLogic { loopStart: .zero, duration: viewModel.duration) loopingPlayerView.play() + loopingPlayerView.player?.isMuted = soundButton.isSelected nextButton.isEnabled = viewModel.canNext + + if !viewModel.canNext { + Toast.shared.showToast(message: "3초 ~ 60초의 영상만 올릴 수 있어요 👀") + } } } diff --git a/iOS/Layover/Layover/Scenes/UIComponents/Toast.swift b/iOS/Layover/Layover/Scenes/UIComponents/Toast.swift index 56ec2e3..b8f5d71 100644 --- a/iOS/Layover/Layover/Scenes/UIComponents/Toast.swift +++ b/iOS/Layover/Layover/Scenes/UIComponents/Toast.swift @@ -40,7 +40,8 @@ final class Toast { let windowScene: UIWindowScene? = scenes.first as? UIWindowScene guard let window: UIWindow = windowScene?.windows.first else { return } let toastLabel: ToastLabel = ToastLabel() - toastLabel.backgroundColor = .background + toastLabel.alpha = 0 + toastLabel.backgroundColor = .background.withAlphaComponent(0.8) toastLabel.textColor = .layoverWhite toastLabel.textAlignment = .center toastLabel.font = .loFont(type: .body2) @@ -48,14 +49,18 @@ final class Toast { toastLabel.layer.cornerRadius = 8 toastLabel.clipsToBounds = true toastLabel.numberOfLines = 1 - toastLabel.layer.opacity = 0.8 toastLabel.frame.size = toastLabel.intrinsicContentSize window.addSubview(toastLabel) toastLabel.center = window.center - UIView.animate(withDuration: 2.0, delay: 0.1, options: .curveEaseOut, animations: { - toastLabel.alpha = 0.0 + + UIView.animate(withDuration: 0.3, delay: 0.5, options: .curveEaseOut, animations: { + toastLabel.alpha = 1.0 }, completion: { _ in - toastLabel.removeFromSuperview() + UIView.animate(withDuration: 1.0, delay: 1.0, options: .curveEaseOut, animations: { + toastLabel.alpha = 0.0 + }, completion: { _ in + toastLabel.removeFromSuperview() + }) }) } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 0836844..2b789ef 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -77,6 +77,8 @@ final class UploadPostViewController: BaseViewController { private let currentAddressLabel: UILabel = { let label = UILabel() label.font = .loFont(type: .body2) + label.numberOfLines = 1 + label.adjustsFontSizeToFitWidth = true return label }() @@ -143,10 +145,7 @@ final class UploadPostViewController: BaseViewController { super.setConstraints() view.addSubviews(scrollView, uploadButton) scrollView.addSubview(contentView) - [scrollView, uploadButton, contentView].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - } - + [scrollView, uploadButton, contentView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), @@ -164,16 +163,13 @@ final class UploadPostViewController: BaseViewController { uploadButton.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), uploadButton.heightAnchor.constraint(equalToConstant: 50) ]) - setContentViewSubviewsConstraints() } private func setContentViewSubviewsConstraints() { contentView.addSubviews(thumbnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, locationImageLabel, currentAddressLabel, contentImageLabel, contentTextView) - contentView.subviews.forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - } + contentView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ thumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), thumbnailImageView.widthAnchor.constraint(equalToConstant: 156), @@ -204,15 +200,16 @@ final class UploadPostViewController: BaseViewController { locationImageLabel.topAnchor.constraint(equalTo: tagStackView.bottomAnchor, constant: 22), locationImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + locationImageLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 60), locationImageLabel.heightAnchor.constraint(equalToConstant: 22), currentAddressLabel.centerYAnchor.constraint(equalTo: locationImageLabel.centerYAnchor), + currentAddressLabel.leadingAnchor.constraint(equalTo: locationImageLabel.trailingAnchor, constant: 15), currentAddressLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - currentAddressLabel.leadingAnchor.constraint(equalTo: locationImageLabel.trailingAnchor), contentImageLabel.topAnchor.constraint(equalTo: locationImageLabel.bottomAnchor, constant: 22), contentImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - contentImageLabel.trailingAnchor.constraint(equalTo: currentAddressLabel.leadingAnchor), + contentImageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), contentImageLabel.heightAnchor.constraint(equalToConstant: 22), contentTextView.topAnchor.constraint(equalTo: contentImageLabel.bottomAnchor, constant: 10), diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 7719d91..8d91918 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -62,7 +62,7 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { return true } catch { os_log(.error, log: .data, "Failed to upload Video: %@", error.localizedDescription) - NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) + NotificationCenter.default.post(name: .uploadTaskDidFail, object: nil) return false } }