Skip to content

Commit

Permalink
✨ 업로드 네트워크 연결, 프로그레스 바 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
anyukyung committed Dec 5, 2023
1 parent 544660a commit f6b36e3
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 72 deletions.
6 changes: 6 additions & 0 deletions iOS/Layover/Layover/DesignSystem/LOTagStackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ final class LOTagStackView: UIStackView {

// MARK: - Properties

var tags: [String] {
arrangedSubviews
.map { $0 as? UIButton }
.compactMap(\.?.titleLabel?.text)
}

private let style: Style

// MARK: - Initializer
Expand Down
2 changes: 2 additions & 0 deletions iOS/Layover/Layover/Extensions/Notification.Name+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ import Foundation

extension Notification.Name {
static let refreshTokenDidExpired = Notification.Name("refreshTokenDidExpired")
static let uploadTaskStart = Notification.Name("uploadTaskStart")
static let progressChanged = Notification.Name("progressChanged")
}
2 changes: 1 addition & 1 deletion iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

struct UploadPostDTO: Decodable {
let id: Int
let title
let title: String
let content: String?
let latitude, longitude: Double
let tag: [String]
Expand Down
11 changes: 10 additions & 1 deletion iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@
import Foundation

struct UploadVideoDTO: Decodable {
let presignedUrl: String
let preSignedURL: String

enum CodingKeys: String, CodingKey {
case preSignedURL = "preSignedUrl"
}
}

struct UploadVideoRequestDTO: Encodable {
let boardID: Int
let filetype: String

enum CodingKeys: String, CodingKey {
case boardID = "boardId"
case filetype
}
}
3 changes: 2 additions & 1 deletion iOS/Layover/Layover/Network/Provider/Provider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ class Provider: ProviderType {
guard let url = URL(string: url) else { throw NetworkError.components }
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)
request.httpMethod = method.rawValue
let backgroundSession = URLSession(configuration: .background(withIdentifier: UUID().uuidString),
request.setValue("video/\(fromFile.pathExtension)", forHTTPHeaderField: "Content-Type")
let backgroundSession = URLSession(configuration: .default,
delegate: sessionTaskDelegate,
delegateQueue: delegateQueue)
let (data, response) = try await backgroundSession.upload(for: request, fromFile: fromFile)
Expand Down
45 changes: 42 additions & 3 deletions iOS/Layover/Layover/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import KakaoSDKUser

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var progressView: UIProgressView = UIProgressView(progressViewStyle: .bar)
var window: UIWindow?


Expand All @@ -19,7 +20,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

let window = UIWindow(windowScene: windowScene)
let rootViewController = AuthManager.shared.isLoggedIn ? MainTabBarViewController() : LoginViewController()
window.rootViewController = UINavigationController(rootViewController: rootViewController)
window.rootViewController = MainTabBarViewController()
self.window = window
addNotificationObservers()
window.makeKeyAndVisible()
Expand Down Expand Up @@ -70,12 +71,26 @@ extension SceneDelegate {
selector: #selector(routeToLoginViewController),
name: .refreshTokenDidExpired,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(uploadTaskStart),
name: .uploadTaskStart,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(progressChanged),
name: .progressChanged,
object: nil)
}

private func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self,
name: .refreshTokenDidExpired,
object: nil)
name: .refreshTokenDidExpired,
object: nil)
NotificationCenter.default.removeObserver(self,
name: .refreshTokenDidExpired,
object: nil)
NotificationCenter.default.removeObserver(self,
name: .progressChanged,
object: nil)
}

@objc private func routeToLoginViewController() {
Expand All @@ -84,5 +99,29 @@ extension SceneDelegate {
rootNavigationViewController.setNavigationBarHidden(false, animated: false)
rootNavigationViewController.setViewControllers([LoginViewController()], animated: true)
}

@objc private func uploadTaskStart() {
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
progressView.progress = 0
progressView.tintColor = .primaryPurple
progressView.frame = CGRect(x: 0,
y: (windowHeight - tabBarHeight - 2),
width: progressViewWidth,
height: 2)
window?.addSubview(progressView)
}


@objc private func progressChanged(_ notification: Notification) {
guard let progress = notification.userInfo?["progress"] as? Float else { return }
progressView.setProgress(progress, animated: true)
if progress == 1 {
progressView.removeFromSuperview()
Toast.shared.showToast(message: "업로드가 완료되었습니다 ✨")
}
}
}

12 changes: 7 additions & 5 deletions iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protocol EditTagDataPassing {
var dataStore: EditTagDataStore? { get }
}

class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing {
final class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing {

// MARK: - Properties

Expand All @@ -27,10 +27,12 @@ class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing {
// MARK: - Routing

func routeToBack() {
guard let navigationController = viewController?.presentingViewController as? UINavigationController,
let destination = navigationController.viewControllers.last as? UploadPostViewController,
var destinationDataStore = destination.router?.dataStore else { return }
destinationDataStore.tags = dataStore?.tags
guard let presentingViewController = viewController?.presentingViewController as? UITabBarController,
let selectedViewController = presentingViewController.selectedViewController as? UINavigationController,
let previousViewController = selectedViewController.viewControllers.last as? UploadPostViewController,
var destination = previousViewController.router?.dataStore
else { return }
destination.tags = dataStore?.tags
viewController?.dismiss(animated: true)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ final class EditTagViewController: BaseViewController {
// MARK: - Methods

@objc private func closeButtonDidTap() {
let buttons = tagStackView.arrangedSubviews.map { $0 as? UIButton }
let tags = buttons.compactMap(\.?.titleLabel?.text)
interactor?.editTag(request: EditTagModels.EditTag.Request(tags: tags))
let request = EditTagModels.EditTag.Request(tags: tagStackView.tags)
interactor?.editTag(request: request)
router?.routeToBack()
}

Expand All @@ -110,7 +109,6 @@ extension EditTagViewController: UITextFieldDelegate {
}
}


extension EditTagViewController: EditTagDisplayLogic {

func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class EditVideoViewController: BaseViewController {
return button
}()

private let nextButton: LOButton = {
private lazy var nextButton: LOButton = {
let button = LOButton(style: .basic)
button.setTitle("다음", for: .normal)
button.addTarget(self, action: #selector(nextButtonDidTap), for: .touchUpInside)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ final class UploadPostConfigurator: Configurator {
let interactor = UploadPostInteractor()
let presenter = UploadPostPresenter()
let router = UploadPostRouter()
let worker = UploadPostWorker()

worker.sessionTaskDelegate = interactor
router.viewController = viewController
router.dataStore = interactor
viewController.interactor = interactor
viewController.router = router
interactor.presenter = presenter
interactor.worker = worker
presenter.viewController = viewController
}

Expand Down
80 changes: 60 additions & 20 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ protocol UploadPostBusinessLogic {
func fetchThumbnailImage()
func fetchCurrentAddress()
func canUploadPost(request: UploadPostModels.CanUploadPost.Request)
func uploadPost(request: UploadPostModels.UploadPost.Request)

@discardableResult
func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task<Bool, Never>
}

protocol UploadPostDataStore {
Expand All @@ -26,13 +28,13 @@ protocol UploadPostDataStore {
var tags: [String]? { get set }
}

final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {
final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostDataStore {

// MARK: - Properties

typealias Models = UploadPostModels

lazy var worker = UploadPostWorker()
var worker: UploadPostWorkerProtocol?
var presenter: UploadPostPresentationLogic?

private let fileManager: FileManager
Expand All @@ -56,7 +58,7 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {

func fetchTags() {
guard let tags else { return }
presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags))
presenter?.presentTags(with: Models.FetchTags.Response(tags: tags))
}

func fetchThumbnailImage() {
Expand All @@ -68,7 +70,7 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {
do {
let image = try await generator.image(at: .zero).image
await MainActor.run {
presenter?.presentThumnailImage(with: UploadPostModels.FetchThumbnail.Response(thumnailImage: image))
presenter?.presentThumnailImage(with: Models.FetchThumbnail.Response(thumnailImage: image))
}
} catch let error {
os_log(.error, log: .default, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription)
Expand All @@ -77,12 +79,7 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {
}

func fetchCurrentAddress() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()

guard let space = locationManager.location?.coordinate else { return }
let location = CLLocation(latitude: space.latitude,
longitude: space.longitude)
guard let location = getCurrentLocation() else { return }
let locale = Locale(identifier: "ko_KR")

Task {
Expand All @@ -104,22 +101,42 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {
}

func canUploadPost(request: UploadPostModels.CanUploadPost.Request) {
let response = UploadPostModels.CanUploadPost.Response(isEmpty: request.title == nil)
let response = Models.CanUploadPost.Response(isEmpty: request.title == nil)
presenter?.presentUploadButton(with: response)
}

func uploadPost(request: UploadPostModels.UploadPost.Request) {
guard let videoURL, let isMuted else { return }
if isMuted {
exportVideoWithoutAudio(at: videoURL)
@discardableResult
func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task<Bool, Never> {
Task {
guard let worker,
let videoURL,
let isMuted,
let coordinate = getCurrentLocation()?.coordinate else { return false }
if isMuted {
exportVideoWithoutAudio(at: videoURL)
}
await MainActor.run {
NotificationCenter.default.post(name: .uploadTaskStart, object: nil)
}
let response = await worker.uploadPost(with: UploadPost(title: request.title,
content: request.content,
latitude: coordinate.latitude,
longitude: coordinate.longitude,
tag: request.tags,
videoURL: videoURL))
return response
}
}

private func getCurrentLocation() -> CLLocation? {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()



guard let space = locationManager.location?.coordinate else { return nil }
let location = CLLocation(latitude: space.latitude, longitude: space.longitude)
return location
}

// isMuted가 true인 경우, 새로운 Composition을 만들어서 영상 재추출해서 업로드
private func exportVideoWithoutAudio(at url: URL) {
let composition = AVMutableComposition()
let sourceAsset = AVURLAsset(url: url)
Expand All @@ -145,9 +162,32 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore {
exporter?.outputFileType = AVFileType.mov
await exporter?.export()
} catch {
os_log(.error, log: .default, "Failed to extract Video Without Audio with error: %@", error.localizedDescription)
os_log(.error, log: .data, "Failed to extract Video Without Audio with error: %@", error.localizedDescription)
}
}
}

}

extension UploadPostInteractor: URLSessionTaskDelegate {

func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
let uploadProgress: Float = Float(Double(totalBytesSent) / Double(totalBytesExpectedToSend))
DispatchQueue.main.async {
NotificationQueue.default.enqueue(Notification(name: .progressChanged,
userInfo: ["progress": uploadProgress]),
postingStyle: .now)
}
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
// TODO: 완료 토스트

}
}
2 changes: 0 additions & 2 deletions iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ enum UploadPostModels {
struct Request {
let title: String
let content: String?
let latitude: Double
let longitude: Double
let tags: [String]
}
struct Response {
Expand Down
Loading

0 comments on commit f6b36e3

Please sign in to comment.