From c17d71494c5fdb0983625097ada60e12368259d2 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:45:13 +0900 Subject: [PATCH 01/25] =?UTF-8?q?:sparkles:=20=EC=8B=A0=EA=B3=A0=20action?= =?UTF-8?q?=20sheet=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Playback/PlaybackViewController.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift index 517e699..d3128e4 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift @@ -41,6 +41,16 @@ final class PlaybackViewController: BaseViewController { return collectionView }() + private let reportButton: UIBarButtonItem = { + let button: UIButton = UIButton() + button.setImage(UIImage(systemName: "ellipsis"), for: .normal) + let barButtonItem: UIBarButtonItem = UIBarButtonItem(customView: button) + barButtonItem.customView?.transform = CGAffineTransform(rotationAngle: .pi / 2) + barButtonItem.target = PlaybackViewController.self + barButtonItem.action = #selector(reportButtonDidTap) + return barButtonItem + }() + // MARK: - Properties private var playerSlider: LOSlider = LOSlider() @@ -115,6 +125,10 @@ final class PlaybackViewController: BaseViewController { override func setUI() { super.setUI() addWindowPlayerSlider() + guard let button = reportButton.customView as? UIButton else { return } + button.addTarget(self, action: #selector(reportButtonDidTap), for: .touchUpInside) + self.navigationItem.rightBarButtonItem = reportButton + self.navigationController?.navigationBar.tintColor = .layoverWhite } private func addWindowPlayerSlider() { @@ -159,6 +173,20 @@ final class PlaybackViewController: BaseViewController { let request: Models.SeekVideo.Request = Models.SeekVideo.Request(currentLocation: Float64(sender.value)) interactor?.controlPlaybackMovie(with: request) } + + @objc private func reportButtonDidTap() { + let alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let reportAction: UIAlertAction = UIAlertAction(title: "신고", style: .destructive, handler: { + action in +// self.reportPlaybackVideo() + }) + let cancelAction: UIAlertAction = UIAlertAction(title: "취소", style: .cancel) + alert.addAction(reportAction) + alert.addAction(cancelAction) + self.present(alert, animated: true, completion: { + self.interactor?.leavePlaybackView() + }) + } } extension PlaybackViewController: PlaybackDisplayLogic { From cd5602ffbe97c515adcc5299dc305fd72f879d72 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:55:06 +0900 Subject: [PATCH 02/25] =?UTF-8?q?:sparkles:=20Report=20Scene=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 36 ++++++++++++++ .../Scenes/Report/ReportConfigurator.swift | 31 ++++++++++++ .../Scenes/Report/ReportInteractor.swift | 26 ++++++++++ .../Layover/Scenes/Report/ReportModels.swift | 14 ++++++ .../Scenes/Report/ReportPresenter.swift | 21 +++++++++ .../Layover/Scenes/Report/ReportRouter.swift | 30 ++++++++++++ .../Scenes/Report/ReportViewController.swift | 47 +++++++++++++++++++ .../Layover/Scenes/Report/ReportWorker.swift | 17 +++++++ 8 files changed, 222 insertions(+) create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportModels.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportRouter.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportViewController.swift create mode 100644 iOS/Layover/Layover/Scenes/Report/ReportWorker.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index fca1443..025018c 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -65,6 +65,13 @@ 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */; }; 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */; }; 19E79AC02B0A85D0009EA9ED /* LoopingPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E79ABF2B0A85D0009EA9ED /* LoopingPlayerView.swift */; }; + 8321A2EE2B1E1026000A12AF /* ReportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2E82B1E1026000A12AF /* ReportPresenter.swift */; }; + 8321A2EF2B1E1026000A12AF /* ReportWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2E92B1E1026000A12AF /* ReportWorker.swift */; }; + 8321A2F02B1E1026000A12AF /* ReportRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2EA2B1E1026000A12AF /* ReportRouter.swift */; }; + 8321A2F12B1E1026000A12AF /* ReportModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2EB2B1E1026000A12AF /* ReportModels.swift */; }; + 8321A2F22B1E1026000A12AF /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2EC2B1E1026000A12AF /* ReportViewController.swift */; }; + 8321A2F32B1E1026000A12AF /* ReportInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2ED2B1E1026000A12AF /* ReportInteractor.swift */; }; + 8321A2F52B1E10DF000A12AF /* ReportConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F42B1E10DF000A12AF /* ReportConfigurator.swift */; }; 834B7BD52B14F888002BD176 /* MockSignUpWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */; }; 835783C32B14A41600E7D304 /* MockLoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835783C22B14A41600E7D304 /* MockLoginWorker.swift */; }; 835783C62B14A5C800E7D304 /* LoginData.json in Resources */ = {isa = PBXBuildFile; fileRef = 835783C52B14A5C800E7D304 /* LoginData.json */; }; @@ -246,6 +253,13 @@ 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = ""; }; 19E79ABF2B0A85D0009EA9ED /* LoopingPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopingPlayerView.swift; sourceTree = ""; }; + 8321A2E82B1E1026000A12AF /* ReportPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportPresenter.swift; sourceTree = ""; }; + 8321A2E92B1E1026000A12AF /* ReportWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportWorker.swift; sourceTree = ""; }; + 8321A2EA2B1E1026000A12AF /* ReportRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRouter.swift; sourceTree = ""; }; + 8321A2EB2B1E1026000A12AF /* ReportModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportModels.swift; sourceTree = ""; }; + 8321A2EC2B1E1026000A12AF /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = ""; }; + 8321A2ED2B1E1026000A12AF /* ReportInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportInteractor.swift; sourceTree = ""; }; + 8321A2F42B1E10DF000A12AF /* ReportConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportConfigurator.swift; sourceTree = ""; }; 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSignUpWorker.swift; sourceTree = ""; }; 835783C22B14A41600E7D304 /* MockLoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginWorker.swift; sourceTree = ""; }; 835783C52B14A5C800E7D304 /* LoginData.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LoginData.json; sourceTree = ""; }; @@ -558,6 +572,20 @@ path = Keychain; sourceTree = ""; }; + 8321A2E72B1E1011000A12AF /* Report */ = { + isa = PBXGroup; + children = ( + 8321A2E82B1E1026000A12AF /* ReportPresenter.swift */, + 8321A2E92B1E1026000A12AF /* ReportWorker.swift */, + 8321A2EA2B1E1026000A12AF /* ReportRouter.swift */, + 8321A2EB2B1E1026000A12AF /* ReportModels.swift */, + 8321A2EC2B1E1026000A12AF /* ReportViewController.swift */, + 8321A2ED2B1E1026000A12AF /* ReportInteractor.swift */, + 8321A2F42B1E10DF000A12AF /* ReportConfigurator.swift */, + ); + path = Report; + sourceTree = ""; + }; 835A61962B0680FC002F22A5 /* Playback */ = { isa = PBXGroup; children = ( @@ -792,6 +820,7 @@ FC7E457B2AFF6F9D004F155A /* Scenes */ = { isa = PBXGroup; children = ( + 8321A2E72B1E1011000A12AF /* Report */, 1945520E2B03AEA400299768 /* Configurator.swift */, 836C33922B18436A00ECAFB0 /* SettingScene */, 19BB8A572B07BEE30070B922 /* UIComponents */, @@ -1059,6 +1088,7 @@ 194552222B0478B400299768 /* HomeWorker.swift in Sources */, 194C21BD2B1B728600C62645 /* StubAuthManager.swift in Sources */, FC5BE1202B148D170036366D /* EditProfileViewController.swift in Sources */, + 8321A2EE2B1E1026000A12AF /* ReportPresenter.swift in Sources */, 194552282B0479B600299768 /* BaseViewController.swift in Sources */, 194552212B0478B400299768 /* HomePresenter.swift in Sources */, 1972CCDA2B13A4BA00C3C762 /* SignUpWorker.swift in Sources */, @@ -1077,6 +1107,7 @@ FC5BE1212B148D170036366D /* EditProfileInteractor.swift in Sources */, FC930E7C2B0CD80800AA48E3 /* ProfileConfigurator.swift in Sources */, 836C33992B1843BE00ECAFB0 /* SettingScenePresenter.swift in Sources */, + 8321A2F12B1E1026000A12AF /* ReportModels.swift in Sources */, FC2511A62B049020004717BC /* SignUpConfigurator.swift in Sources */, 194552392B05230E00299768 /* HomeCarouselCollectionViewCell.swift in Sources */, 836C338F2B160CC700ECAFB0 /* MemberDTO.swift in Sources */, @@ -1093,6 +1124,7 @@ FC930E772B0CD75C00AA48E3 /* ProfileRouter.swift in Sources */, FC0E80302B1A15D600EF56D6 /* LOImageLabel.swift in Sources */, FC5BE11F2B148D160036366D /* EditProfileModels.swift in Sources */, + 8321A2EF2B1E1026000A12AF /* ReportWorker.swift in Sources */, 194551F42B037F2D00299768 /* LoginRouter.swift in Sources */, FC4975992B03439000D8627F /* UIFont+.swift in Sources */, FC5BE11E2B148D160036366D /* EditProfileRouter.swift in Sources */, @@ -1119,7 +1151,9 @@ FC7E453A2AFEB623004F155A /* AppDelegate.swift in Sources */, FC0E80282B1A0BBB00EF56D6 /* UploadPostViewController.swift in Sources */, 1972CCD82B13A2EC00C3C762 /* SignUpEndPointFactory.swift in Sources */, + 8321A2F32B1E1026000A12AF /* ReportInteractor.swift in Sources */, 19A169262B176C5F00DB34C0 /* TagPlayListModels.swift in Sources */, + 8321A2F22B1E1026000A12AF /* ReportViewController.swift in Sources */, FC767F842B1214A80088CF9B /* MockUserWorker.swift in Sources */, 836C338B2B15D22C00ECAFB0 /* PlaybackConfigurator.swift in Sources */, FC930E782B0CD75C00AA48E3 /* ProfileModels.swift in Sources */, @@ -1151,6 +1185,7 @@ FC0E80322B1A280300EF56D6 /* LOTextView.swift in Sources */, 19A169282B176C5F00DB34C0 /* TagPlayListInteractor.swift in Sources */, FC930E7A2B0CD75C00AA48E3 /* ProfileInteractor.swift in Sources */, + 8321A2F02B1E1026000A12AF /* ReportRouter.swift in Sources */, FC930E752B0CD75C00AA48E3 /* ProfilePresenter.swift in Sources */, 835A61A12B068115002F22A5 /* PlaybackViewController.swift in Sources */, 1945520F2B03AEA400299768 /* Configurator.swift in Sources */, @@ -1163,6 +1198,7 @@ FCEE0FFA2B03AF8500195BBE /* SignUpViewController.swift in Sources */, 194551F32B037F2D00299768 /* LoginWorker.swift in Sources */, FC0E803D2B1B91C900EF56D6 /* EditTagModels.swift in Sources */, + 8321A2F52B1E10DF000A12AF /* ReportConfigurator.swift in Sources */, 835A61902B067D61002F22A5 /* LOSlider.swift in Sources */, 19E79AC02B0A85D0009EA9ED /* LoopingPlayerView.swift in Sources */, 1972CCCF2B12438900C3C762 /* LoginEndPointFactory.swift in Sources */, diff --git a/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift b/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift new file mode 100644 index 0000000..93566fd --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift @@ -0,0 +1,31 @@ +// +// ReportConfigurator.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +final class ReportConfigurator: Configurator { + static let shared = ReportConfigurator() + + private init() { } + + func configure(_ viewController: ReportViewController) { + let viewController: ReportViewController = viewController + let interactor: ReportInteractor = ReportInteractor() + let presenter: ReportPresenter = ReportPresenter() + let worker: ReportWorker = ReportWorker() + let router: ReportRouter = ReportRouter() + + router.viewController = viewController + router.dataStore = interactor + viewController.interactor = interactor + viewController.router = router + interactor.presenter = presenter + interactor.worker = worker + presenter.viewController = viewController + } +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift new file mode 100644 index 0000000..3984cd8 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift @@ -0,0 +1,26 @@ +// +// ReportInteractor.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol ReportBusinessLogic { +} + +protocol ReportDataStore { +} + +final class ReportInteractor: ReportBusinessLogic, ReportDataStore { + + // MARK: - Properties + + typealias Models = ReportModels + + lazy var worker = ReportWorker() + var presenter: ReportPresentationLogic? + +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportModels.swift b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift new file mode 100644 index 0000000..8a0cdc1 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift @@ -0,0 +1,14 @@ +// +// ReportModels.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +enum ReportModels { + + // MARK: - Use Cases +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift new file mode 100644 index 0000000..f798a0c --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift @@ -0,0 +1,21 @@ +// +// ReportPresenter.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +protocol ReportPresentationLogic { +} + +final class ReportPresenter: ReportPresentationLogic { + + // MARK: - Properties + + typealias Models = ReportModels + weak var viewController: ReportDisplayLogic? + +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportRouter.swift b/iOS/Layover/Layover/Scenes/Report/ReportRouter.swift new file mode 100644 index 0000000..99b8bd5 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportRouter.swift @@ -0,0 +1,30 @@ +// +// ReportRouter.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol ReportRoutingLogic { + func routeToNext() +} + +protocol ReportDataPassing { + var dataStore: ReportDataStore? { get } +} + +final class ReportRouter: NSObject, ReportRoutingLogic, ReportDataPassing { + + // MARK: - Properties + + weak var viewController: ReportViewController? + var dataStore: ReportDataStore? + + // MARK: - Routing + + func routeToNext() { + } +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift new file mode 100644 index 0000000..f14f2e3 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift @@ -0,0 +1,47 @@ +// +// ReportViewController.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol ReportDisplayLogic: AnyObject { + +} + +final class ReportViewController: BaseViewController, ReportDisplayLogic { + + // MARK: - Properties + + typealias Models = ReportModels + var router: (NSObjectProtocol & ReportRoutingLogic & ReportDataPassing)? + var interactor: ReportBusinessLogic? + + // MARK: - Object lifecycle + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + // MARK: - Setup + + private func setup() { + ReportConfigurator.shared.configure(self) + } + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + } + +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift new file mode 100644 index 0000000..471687e --- /dev/null +++ b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift @@ -0,0 +1,17 @@ +// +// ReportWorker.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +final class ReportWorker { + + // MARK: - Properties + + typealias Models = ReportModels + +} From 29aa19f3af73be065beb1cdc7d0b8f4a2f387a7c Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 00:04:01 +0900 Subject: [PATCH 03/25] =?UTF-8?q?:sparkles:=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20UI=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 12 ++++ .../Layover/DesignSystem/LOPopUpView.swift | 71 +++++++++++++++++++ .../DesignSystem/LOReportContentView.swift | 47 ++++++++++++ .../DesignSystem/LOReportStackView.swift | 39 ++++++++++ .../Scenes/Report/ReportViewController.swift | 41 +++++++++++ 5 files changed, 210 insertions(+) create mode 100644 iOS/Layover/Layover/DesignSystem/LOPopUpView.swift create mode 100644 iOS/Layover/Layover/DesignSystem/LOReportContentView.swift create mode 100644 iOS/Layover/Layover/DesignSystem/LOReportStackView.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 025018c..4f97908 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -72,6 +72,9 @@ 8321A2F22B1E1026000A12AF /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2EC2B1E1026000A12AF /* ReportViewController.swift */; }; 8321A2F32B1E1026000A12AF /* ReportInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2ED2B1E1026000A12AF /* ReportInteractor.swift */; }; 8321A2F52B1E10DF000A12AF /* ReportConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F42B1E10DF000A12AF /* ReportConfigurator.swift */; }; + 8321A2F72B1E14A1000A12AF /* LOPopUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */; }; + 8321A2F92B1E15F3000A12AF /* LOReportStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */; }; + 8321A2FB2B1E1739000A12AF /* LOReportContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */; }; 834B7BD52B14F888002BD176 /* MockSignUpWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */; }; 835783C32B14A41600E7D304 /* MockLoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835783C22B14A41600E7D304 /* MockLoginWorker.swift */; }; 835783C62B14A5C800E7D304 /* LoginData.json in Resources */ = {isa = PBXBuildFile; fileRef = 835783C52B14A5C800E7D304 /* LoginData.json */; }; @@ -260,6 +263,9 @@ 8321A2EC2B1E1026000A12AF /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = ""; }; 8321A2ED2B1E1026000A12AF /* ReportInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportInteractor.swift; sourceTree = ""; }; 8321A2F42B1E10DF000A12AF /* ReportConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportConfigurator.swift; sourceTree = ""; }; + 8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOPopUpView.swift; sourceTree = ""; }; + 8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOReportStackView.swift; sourceTree = ""; }; + 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOReportContentView.swift; sourceTree = ""; }; 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSignUpWorker.swift; sourceTree = ""; }; 835783C22B14A41600E7D304 /* MockLoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginWorker.swift; sourceTree = ""; }; 835783C52B14A5C800E7D304 /* LoginData.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LoginData.json; sourceTree = ""; }; @@ -852,6 +858,9 @@ 835A61932B068096002F22A5 /* LODescriptionView.swift */, FC0E802F2B1A15D500EF56D6 /* LOImageLabel.swift */, FC0E80312B1A280300EF56D6 /* LOTextView.swift */, + 8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */, + 8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */, + 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */, ); path = DesignSystem; sourceTree = ""; @@ -1118,6 +1127,7 @@ 194552242B0478B400299768 /* HomeModels.swift in Sources */, 19A1692D2B17750B00DB34C0 /* Post.swift in Sources */, 194552022B038B8300299768 /* OSLog+.swift in Sources */, + 8321A2F92B1E15F3000A12AF /* LOReportStackView.swift in Sources */, FC0E80272B1A0BBB00EF56D6 /* UploadPostModels.swift in Sources */, 194552312B04DA1A00299768 /* LOCircleButton.swift in Sources */, 194552262B0478B400299768 /* HomeInteractor.swift in Sources */, @@ -1144,6 +1154,7 @@ 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */, 193686742B15C489008902CD /* UserWorker.swift in Sources */, 19A169242B176C5F00DB34C0 /* TagPlayListWorker.swift in Sources */, + 8321A2FB2B1E1739000A12AF /* LOReportContentView.swift in Sources */, FCE52FF82B0FCAF7002CDB75 /* MockURLProtocol.swift in Sources */, FC930E792B0CD75C00AA48E3 /* ProfileViewController.swift in Sources */, 194C21BB2B1B718B00C62645 /* MockHomeWorker.swift in Sources */, @@ -1218,6 +1229,7 @@ FC68E2A52B0233D3001AABFF /* Provider.swift in Sources */, FC930E7E2B0CDB2900AA48E3 /* ThumbnailCollectionViewCell.swift in Sources */, 194552112B03AF2B00299768 /* MainTabBarConfigurator.swift in Sources */, + 8321A2F72B1E14A1000A12AF /* LOPopUpView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift new file mode 100644 index 0000000..fd44308 --- /dev/null +++ b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift @@ -0,0 +1,71 @@ +// +// LOPopUpView.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class LOPopUpView: UIView { + private let titleLabel: UILabel = { + let label: UILabel = UILabel() + label.text = "콘텐츠 신고" + label.font = .loFont(type: .subtitle) + return label + }() + + private let reportStackView: LOReportStackView = LOReportStackView() + + private let cancelButton: UIButton = { + let button: UIButton = UIButton() + button.setTitle("취소", for: .normal) + button.setTitleColor(.grey200, for: .normal) + return button + }() + + private let reportButton: UIButton = { + let button: UIButton = UIButton() + button.setTitle("신고", for: .normal) + button.setTitleColor(.error, for: .normal) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setConstraints() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setConstraints() + } + + private func setConstraints() { + addSubviews(titleLabel, reportStackView, cancelButton, reportButton) + subviews.forEach { subView in + subView.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 28), + titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 28), + titleLabel.bottomAnchor.constraint(equalTo: reportStackView.topAnchor, constant: -5), + reportStackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1), + reportStackView.heightAnchor.constraint(equalToConstant: 336), + reportStackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5), + cancelButton.leadingAnchor.constraint(equalTo: self.leadingAnchor), + cancelButton.trailingAnchor.constraint(equalTo: reportButton.leadingAnchor), + cancelButton.widthAnchor.constraint(equalToConstant: 175), + cancelButton.heightAnchor.constraint(equalToConstant: 56), + cancelButton.topAnchor.constraint(equalTo: reportStackView.bottomAnchor), + reportButton.leadingAnchor.constraint(equalTo: cancelButton.trailingAnchor), + reportButton.trailingAnchor.constraint(equalTo: self.trailingAnchor), + reportButton.widthAnchor.constraint(equalToConstant: 175), + reportButton.heightAnchor.constraint(equalToConstant: 56), + reportButton.topAnchor.constraint(equalTo: reportStackView.bottomAnchor) + ]) + + } +} diff --git a/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift new file mode 100644 index 0000000..7beb003 --- /dev/null +++ b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift @@ -0,0 +1,47 @@ +// +// LOReportContentView.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class LOReportContentView: UIView { + private let radioButton: LOCircleButton = LOCircleButton(style: .add, diameter: 24) + private let contentLabel: UILabel = { + let label: UILabel = UILabel() + label.text = "스팸 / 홍보 도배글이에요" + label.font = .loFont(type: .body3) + label.textAlignment = .left + label.textColor = .layoverWhite + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setConstraints() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setConstraints() + } + + func setText(_ content: String) { + contentLabel.text = content + } + private func setConstraints() { + addSubviews(radioButton, contentLabel) + subviews.forEach { subView in + subView.translatesAutoresizingMaskIntoConstraints = false + } + NSLayoutConstraint.activate([ + radioButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), + radioButton.centerYAnchor.constraint(equalTo: self.centerYAnchor), + radioButton.trailingAnchor.constraint(equalTo: contentLabel.leadingAnchor, constant: -12), + contentLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor) + ]) + } +} diff --git a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift new file mode 100644 index 0000000..b451f1a --- /dev/null +++ b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift @@ -0,0 +1,39 @@ +// +// LOReportStackView.swift +// Layover +// +// Created by 황지웅 on 12/4/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +final class LOReportStackView: UIStackView { + override init(frame: CGRect) { + super.init(frame: frame) + setUI() + addContent() + } + + required init(coder: NSCoder) { + super.init(coder: coder) + setUI() + addContent() + } + + private func setUI() { + self.alignment = .fill + self.distribution = .fillProportionally + self.axis = .vertical + self.spacing = 8 + } + + private func addContent() { + let contentArray: [String] = ["스팸 홍보 / 도배글이에요", "부적절한 사진이에요", "청소년에게 유해한 내용이에요", "욕설 / 혐오 / 차별적 표현을 담고있어요", "거짓 정보를 담고있어요", "기타"] + contentArray.forEach { content in + let loReportContentView: LOReportContentView = LOReportContentView() + loReportContentView.setText(content) + addArrangedSubview(loReportContentView) + } + } +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift index f14f2e3..6306d7e 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift @@ -14,6 +14,22 @@ protocol ReportDisplayLogic: AnyObject { final class ReportViewController: BaseViewController, ReportDisplayLogic { + // MARK: - UI Components + + private let popUpView: LOPopUpView = { + let view: LOPopUpView = LOPopUpView() + view.layer.cornerRadius = 12 + view.backgroundColor = .darkGrey + return view + }() + + private let backgroundView: UIView = { + let view: UIView = UIView() + view.backgroundColor = .black + view.alpha = 0.5 + return view + }() + // MARK: - Properties typealias Models = ReportModels @@ -44,4 +60,29 @@ final class ReportViewController: BaseViewController, ReportDisplayLogic { super.viewDidLoad() } + override func setUI() { + super.setUI() + } + + override func setConstraints() { + super.setConstraints() + view.addSubviews(backgroundView, popUpView) + view.subviews.forEach { subView in + subView.translatesAutoresizingMaskIntoConstraints = false + } + NSLayoutConstraint.activate([ + backgroundView.topAnchor.constraint(equalTo: view.topAnchor), + backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + popUpView.widthAnchor.constraint(equalToConstant: 350), + popUpView.heightAnchor.constraint(equalToConstant: 450) + ]) + } +} + +#Preview { + ReportViewController() } From 1919c66f1b1bc83c1d9782d0d67e6651a2be44e0 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 01:04:36 +0900 Subject: [PATCH 04/25] =?UTF-8?q?:sparkles:=20radio=20button=20UI=EA=B5=AC?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/DesignSystem/LOPopUpView.swift | 3 +- .../DesignSystem/LOReportContentView.swift | 36 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift index fd44308..0341074 100644 --- a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift @@ -52,9 +52,10 @@ final class LOPopUpView: UIView { titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 28), titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 28), titleLabel.bottomAnchor.constraint(equalTo: reportStackView.topAnchor, constant: -5), - reportStackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1), + reportStackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.8), reportStackView.heightAnchor.constraint(equalToConstant: 336), reportStackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5), + reportStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 13.5), cancelButton.leadingAnchor.constraint(equalTo: self.leadingAnchor), cancelButton.trailingAnchor.constraint(equalTo: reportButton.leadingAnchor), cancelButton.widthAnchor.constraint(equalToConstant: 175), diff --git a/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift index 7beb003..6574de3 100644 --- a/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift @@ -9,11 +9,32 @@ import UIKit final class LOReportContentView: UIView { - private let radioButton: LOCircleButton = LOCircleButton(style: .add, diameter: 24) + + // MARK: - UI Components + + private let whiteCircle: UIView = { + let view: UIView = UIView() + view.backgroundColor = .layoverWhite + view.clipsToBounds = true + view.layer.cornerRadius = 4 + return view + }() + + private let radioButton: UIButton = { + let button: UIButton = UIButton() + button.layer.borderColor = UIColor.grey100.cgColor + button.layer.borderWidth = 0.1 + button.backgroundColor = .clear + button.layer.cornerRadius = 10 + button.clipsToBounds = true + button.backgroundColor = .primaryPurple + return button + }() + private let contentLabel: UILabel = { let label: UILabel = UILabel() label.text = "스팸 / 홍보 도배글이에요" - label.font = .loFont(type: .body3) + label.font = .loFont(type: .body2) label.textAlignment = .left label.textColor = .layoverWhite return label @@ -32,12 +53,23 @@ final class LOReportContentView: UIView { func setText(_ content: String) { contentLabel.text = content } + + // MARK: - UI Methods + private func setConstraints() { + radioButton.addSubview(whiteCircle) + whiteCircle.translatesAutoresizingMaskIntoConstraints = false addSubviews(radioButton, contentLabel) subviews.forEach { subView in subView.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ + whiteCircle.centerXAnchor.constraint(equalTo: radioButton.centerXAnchor), + whiteCircle.centerYAnchor.constraint(equalTo: radioButton.centerYAnchor), + whiteCircle.widthAnchor.constraint(equalToConstant: 8), + whiteCircle.heightAnchor.constraint(equalToConstant: 8), + radioButton.widthAnchor.constraint(equalToConstant: 20), + radioButton.heightAnchor.constraint(equalToConstant: 20), radioButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), radioButton.centerYAnchor.constraint(equalTo: self.centerYAnchor), radioButton.trailingAnchor.constraint(equalTo: contentLabel.leadingAnchor, constant: -12), From 7c116bb928f7bdae08a54cdc316183538920f7b4 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 00:27:23 +0900 Subject: [PATCH 05/25] =?UTF-8?q?=E2=9C=A8=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20vip=20=ED=94=8C=EB=A1=9C=EC=9A=B0,=20?= =?UTF-8?q?=EC=98=81=EC=83=81=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C,=20=EC=98=A4=EB=94=94=EC=98=A4=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditVideo/EditVideoInteractor.swift | 9 +++ .../Scenes/EditVideo/EditVideoModels.swift | 6 ++ .../Scenes/EditVideo/EditVideoRouter.swift | 11 ++- .../EditVideo/EditVideoViewController.swift | 14 +++- .../UploadPost/UploadPostInteractor.swift | 73 ++++++++++++++++++- .../Scenes/UploadPost/UploadPostModels.swift | 29 +++++++- .../UploadPost/UploadPostPresenter.swift | 9 ++- .../UploadPost/UploadPostViewController.swift | 32 +++++--- 8 files changed, 164 insertions(+), 19 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift index 814695e..0709dab 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift @@ -12,10 +12,12 @@ import UIKit protocol EditVideoBusinessLogic { func fetchVideo(request: EditVideoModels.FetchVideo.Request) func deleteVideo() + func didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request) } protocol EditVideoDataStore { var videoURL: URL? { get set } + var isMuted: Bool? { get set } } final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { @@ -27,7 +29,10 @@ final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { var videoFileWorker: VideoFileWorker? var presenter: EditVideoPresentationLogic? + // MARK: - Data Store + var videoURL: URL? + var isMuted: Bool? func fetchVideo(request: EditVideoModels.FetchVideo.Request) { let isEdited = request.editedVideoURL != nil @@ -51,4 +56,8 @@ final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { videoFileWorker?.delete(at: videoURL) } + func didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request) { + isMuted = request.isMuted + } + } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift index 3d65206..7160780 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift @@ -29,4 +29,10 @@ enum EditVideoModels { } } + enum DidFinishViedoEditing { + struct Request { + let isMuted: Bool + } + } + } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift index 74855e0..b7dcf75 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift @@ -26,7 +26,16 @@ final class EditVideoRouter: NSObject, EditVideoRoutingLogic, EditVideoDataPassi // MARK: - Routing func routeToNext() { - + let nextViewController = UploadPostViewController() + guard let source = dataStore, + var destination = nextViewController.router?.dataStore + else { return } + + // Data Passing + destination.videoURL = source.videoURL + destination.isMuted = source.isMuted + nextViewController.hidesBottomBarWhenPushed = true + viewController?.navigationController?.pushViewController(nextViewController, animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index d893c44..3c95a59 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -37,6 +37,7 @@ final class EditVideoViewController: BaseViewController { private let nextButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("다음", for: .normal) + button.addTarget(self, action: #selector(nextButtonDidTap), for: .touchUpInside) return button }() @@ -73,8 +74,13 @@ final class EditVideoViewController: BaseViewController { interactor?.fetchVideo(request: Models.FetchVideo.Request(editedVideoURL: nil)) } + override func viewWillAppear(_ animated: Bool) { + loopingPlayerView.play() + } + override func viewWillDisappear(_ animated: Bool) { - interactor?.deleteVideo() +// interactor?.deleteVideo() + print("viewWillDisappear") } override func setConstraints() { @@ -121,6 +127,12 @@ final class EditVideoViewController: BaseViewController { } } + @objc private func nextButtonDidTap() { + loopingPlayerView.pause() + interactor?.didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request(isMuted: soundButton.isSelected)) + router?.routeToNext() + } + } extension EditVideoViewController: UINavigationControllerDelegate, UIVideoEditorControllerDelegate { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 4b638dd..e1baf08 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -6,17 +6,22 @@ // Copyright © 2023 CodeBomber. All rights reserved. // +import AVFoundation import UIKit -protocol UploadPostBusinessLogic { +import OSLog +protocol UploadPostBusinessLogic { + func fetchThumbnailImage() + func uploadPost() } protocol UploadPostDataStore { - + var videoURL: URL? { get set } + var isMuted: Bool? { get set } } -class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { +final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { // MARK: - Properties @@ -25,4 +30,66 @@ class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { lazy var worker = UploadPostWorker() var presenter: UploadPostPresentationLogic? + // MARK: - Data Store + + var videoURL: URL? + var isMuted: Bool? + + func fetchThumbnailImage() { + guard let videoURL else { return } + let asset = AVAsset(url: videoURL) + let generator = AVAssetImageGenerator(asset: asset) + generator.appliesPreferredTrackTransform = true + Task { + do { + let image = try await generator.image(at: .zero).image + await MainActor.run { + presenter?.presentThumnailImage(with: UploadPostModels.FetchThumbnail.Response(thumnailImage: image)) + } + } catch let error { + os_log(.error, log: .default, "Failed to fetch ThumbnailImage with error: %@", error.localizedDescription) + } + } + } + + func uploadPost() { + guard let isMuted else { return } + if isMuted { + extractVideoWithoutAudio() + } + } + + private func extractVideoWithoutAudio() { + guard let videoURL else { return } + + let composition = AVMutableComposition() + let sourceAsset = AVURLAsset(url: videoURL) + guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, + preferredTrackID: kCMPersistentTrackID_Invalid) else { return } + + Task { + do { + let sourceAssetduration = try await sourceAsset.load(.duration) + let sourceVideoTrack = try await sourceAsset.load(.tracks)[0] + compositionVideoTrack.preferredTransform = try await sourceVideoTrack.load(.preferredTransform) + + let timeRange: CMTimeRange = CMTimeRangeMake(start: .zero, duration: sourceAssetduration) + try compositionVideoTrack.insertTimeRange(timeRange, + of: sourceVideoTrack, + at: .zero) + + if FileManager.default.fileExists(atPath: videoURL.path()) { + try FileManager.default.removeItem(at: videoURL) + } + + let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) + exporter?.outputURL = videoURL + exporter?.outputFileType = AVFileType.mov + await exporter?.export() + } catch let error { + os_log(.error, log: .default, "Failed to fetch extractVideoWithoutAudio with error: %@", error.localizedDescription) + } + } + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 6d904f4..f729e49 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,5 +10,32 @@ import UIKit enum UploadPostModels { - // MARK: - Use Cases + enum FetchThumbnail { + struct Request { + + } + struct Response { + let thumnailImage: CGImage + } + struct ViewModel { + let thumnailImage: UIImage + } + } + + enum UploadPost { + struct Request { + let title: String + let content: String? + let latitude: Double + let longitude: Double + let tags: [String] + } + struct Response { + + } + struct VideModel { + + } + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 1e7dce2..881d455 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -9,14 +9,19 @@ import UIKit protocol UploadPostPresentationLogic { - + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) } -class UploadPostPresenter: UploadPostPresentationLogic { +final class UploadPostPresenter: UploadPostPresentationLogic { // MARK: - Properties typealias Models = UploadPostModels weak var viewController: UploadPostDisplayLogic? + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) { + let image = UIImage(cgImage: response.thumnailImage) + viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 1e76e90..668cceb 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -9,10 +9,10 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { - + func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) } -final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic { +final class UploadPostViewController: BaseViewController { // MARK: - UI Components @@ -24,7 +24,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic private let contentView: UIView = UIView() - private let thumnailImageView: UIImageView = { + private let thumbnailImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true @@ -93,6 +93,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic private let uploadButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) + button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) return button }() @@ -126,6 +127,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic super.viewDidLoad() setConstraints() addTarget() + interactor?.fetchThumbnailImage() } override func setConstraints() { @@ -158,18 +160,18 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic } private func setContentViewSubviewsConstraints() { - contentView.addSubviews(thumnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, + contentView.addSubviews(thumbnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, locationImageLabel, locationLabel, contentImageLabel, contentTextView) contentView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ - thumnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), - thumnailImageView.widthAnchor.constraint(equalToConstant: 156), - thumnailImageView.heightAnchor.constraint(equalToConstant: 251), - thumnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + thumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), + thumbnailImageView.widthAnchor.constraint(equalToConstant: 156), + thumbnailImageView.heightAnchor.constraint(equalToConstant: 251), + thumbnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - titleImageLabel.topAnchor.constraint(equalTo: thumnailImageView.bottomAnchor, constant: 22), + titleImageLabel.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 22), titleImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleImageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleImageLabel.heightAnchor.constraint(equalToConstant: 22), @@ -224,8 +226,16 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic router?.routeToNext() } + @objc private func uploadButtonDidTap() { + interactor?.uploadPost() + } + } -#Preview { - UploadPostViewController() +extension UploadPostViewController: UploadPostDisplayLogic { + + func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) { + thumbnailImageView.image = viewModel.thumnailImage + } + } From b65f03ac10ceee6ca360f1019685153f893a581d Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 02:06:52 +0900 Subject: [PATCH 06/25] =?UTF-8?q?:sparkles:=20radio=20button=20ui=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DesignSystem/LOReportContentView.swift | 52 ++++++++++++------- .../DesignSystem/LOReportStackView.swift | 33 ++++++++++-- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift index 6574de3..58c4ba0 100644 --- a/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOReportContentView.swift @@ -12,26 +12,30 @@ final class LOReportContentView: UIView { // MARK: - UI Components + var index: Int = 0 + private let whiteCircle: UIView = { let view: UIView = UIView() view.backgroundColor = .layoverWhite view.clipsToBounds = true view.layer.cornerRadius = 4 + view.isHidden = true + view.isUserInteractionEnabled = false return view }() - private let radioButton: UIButton = { - let button: UIButton = UIButton() - button.layer.borderColor = UIColor.grey100.cgColor - button.layer.borderWidth = 0.1 - button.backgroundColor = .clear - button.layer.cornerRadius = 10 - button.clipsToBounds = true - button.backgroundColor = .primaryPurple - return button + private let radioView: UIView = { + let view: UIView = UIView() + view.layer.borderColor = UIColor.grey100.cgColor + view.layer.borderWidth = 0.1 + view.backgroundColor = .clear + view.layer.cornerRadius = 10 + view.clipsToBounds = true + view.isUserInteractionEnabled = false + return view }() - private let contentLabel: UILabel = { + let contentLabel: UILabel = { let label: UILabel = UILabel() label.text = "스팸 / 홍보 도배글이에요" label.font = .loFont(type: .body2) @@ -50,6 +54,16 @@ final class LOReportContentView: UIView { setConstraints() } + func onRadio() { + whiteCircle.isHidden = false + radioView.backgroundColor = .primaryPurple + } + + func offRadio() { + whiteCircle.isHidden = true + radioView.backgroundColor = .clear + } + func setText(_ content: String) { contentLabel.text = content } @@ -57,22 +71,22 @@ final class LOReportContentView: UIView { // MARK: - UI Methods private func setConstraints() { - radioButton.addSubview(whiteCircle) + radioView.addSubview(whiteCircle) whiteCircle.translatesAutoresizingMaskIntoConstraints = false - addSubviews(radioButton, contentLabel) + addSubviews(radioView, contentLabel) subviews.forEach { subView in subView.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ - whiteCircle.centerXAnchor.constraint(equalTo: radioButton.centerXAnchor), - whiteCircle.centerYAnchor.constraint(equalTo: radioButton.centerYAnchor), + whiteCircle.centerXAnchor.constraint(equalTo: radioView.centerXAnchor), + whiteCircle.centerYAnchor.constraint(equalTo: radioView.centerYAnchor), whiteCircle.widthAnchor.constraint(equalToConstant: 8), whiteCircle.heightAnchor.constraint(equalToConstant: 8), - radioButton.widthAnchor.constraint(equalToConstant: 20), - radioButton.heightAnchor.constraint(equalToConstant: 20), - radioButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), - radioButton.centerYAnchor.constraint(equalTo: self.centerYAnchor), - radioButton.trailingAnchor.constraint(equalTo: contentLabel.leadingAnchor, constant: -12), + radioView.widthAnchor.constraint(equalToConstant: 20), + radioView.heightAnchor.constraint(equalToConstant: 20), + radioView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), + radioView.centerYAnchor.constraint(equalTo: self.centerYAnchor), + radioView.trailingAnchor.constraint(equalTo: contentLabel.leadingAnchor, constant: -12), contentLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor) ]) } diff --git a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift index b451f1a..aac6bcc 100644 --- a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift @@ -9,6 +9,17 @@ import UIKit final class LOReportStackView: UIStackView { + + var reportViews: [LOReportContentView] = { + let contentArray: [String] = ["스팸 홍보 / 도배글이에요", "부적절한 사진이에요", "청소년에게 유해한 내용이에요", "욕설 / 혐오 / 차별적 표현을 담고있어요", "거짓 정보를 담고있어요", "기타"] + return contentArray.enumerated().map { index, content in + let loReportContentView: LOReportContentView = LOReportContentView() + loReportContentView.setText(content) + loReportContentView.index = index + return loReportContentView + } + }() + override init(frame: CGRect) { super.init(frame: frame) setUI() @@ -26,14 +37,26 @@ final class LOReportStackView: UIStackView { self.distribution = .fillProportionally self.axis = .vertical self.spacing = 8 + guard let firstReportView: LOReportContentView = reportViews.first else { return } + firstReportView.onRadio() } private func addContent() { - let contentArray: [String] = ["스팸 홍보 / 도배글이에요", "부적절한 사진이에요", "청소년에게 유해한 내용이에요", "욕설 / 혐오 / 차별적 표현을 담고있어요", "거짓 정보를 담고있어요", "기타"] - contentArray.forEach { content in - let loReportContentView: LOReportContentView = LOReportContentView() - loReportContentView.setText(content) - addArrangedSubview(loReportContentView) + reportViews.forEach { reportView in + let addGesutre: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(reportViewDidTap(_:))) + reportView.addGestureRecognizer(addGesutre) + addArrangedSubview(reportView) + } + } + + @objc private func reportViewDidTap(_ sender: UITapGestureRecognizer) { + guard let tempView: LOReportContentView = sender.view as? LOReportContentView else { return } + self.reportViews.forEach { reportView in + if reportView.index == tempView.index { + reportView.onRadio() + } else { + reportView.offRadio() + } } } } From a9878d72e24231fb829d6b0bf384ca7df36a239f Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 02:35:42 +0900 Subject: [PATCH 07/25] =?UTF-8?q?:sparkles:=20report=20api=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20endpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 8 ++++++ .../Layover/Network/DTOs/ReportDTO.swift | 15 +++++++++++ .../Factories/ReportEndPointFactory.swift | 27 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 iOS/Layover/Layover/Network/DTOs/ReportDTO.swift create mode 100644 iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 4f97908..f8cdac7 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -75,6 +75,8 @@ 8321A2F72B1E14A1000A12AF /* LOPopUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */; }; 8321A2F92B1E15F3000A12AF /* LOReportStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */; }; 8321A2FB2B1E1739000A12AF /* LOReportContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */; }; + 8321A2FD2B1E4260000A12AF /* ReportEndPointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FC2B1E4260000A12AF /* ReportEndPointFactory.swift */; }; + 8321A2FF2B1E428C000A12AF /* ReportDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */; }; 834B7BD52B14F888002BD176 /* MockSignUpWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */; }; 835783C32B14A41600E7D304 /* MockLoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835783C22B14A41600E7D304 /* MockLoginWorker.swift */; }; 835783C62B14A5C800E7D304 /* LoginData.json in Resources */ = {isa = PBXBuildFile; fileRef = 835783C52B14A5C800E7D304 /* LoginData.json */; }; @@ -266,6 +268,8 @@ 8321A2F62B1E14A1000A12AF /* LOPopUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOPopUpView.swift; sourceTree = ""; }; 8321A2F82B1E15F3000A12AF /* LOReportStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOReportStackView.swift; sourceTree = ""; }; 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOReportContentView.swift; sourceTree = ""; }; + 8321A2FC2B1E4260000A12AF /* ReportEndPointFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportEndPointFactory.swift; sourceTree = ""; }; + 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportDTO.swift; sourceTree = ""; }; 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSignUpWorker.swift; sourceTree = ""; }; 835783C22B14A41600E7D304 /* MockLoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginWorker.swift; sourceTree = ""; }; 835783C52B14A5C800E7D304 /* LoginData.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LoginData.json; sourceTree = ""; }; @@ -455,6 +459,7 @@ 1972CCD72B13A2EC00C3C762 /* SignUpEndPointFactory.swift */, 193686712B15BCA7008902CD /* UserEndPointFactory.swift */, 19A1693F2B17C10300DB34C0 /* PostEndPointFactory.swift */, + 8321A2FC2B1E4260000A12AF /* ReportEndPointFactory.swift */, ); path = Factories; sourceTree = ""; @@ -528,6 +533,7 @@ 19A1693B2B17BD1C00DB34C0 /* BoardDTO.swift */, 836C338C2B15D91F00ECAFB0 /* VideoDTO.swift */, 836C338E2B160CC700ECAFB0 /* MemberDTO.swift */, + 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */, ); path = DTOs; sourceTree = ""; @@ -1072,6 +1078,7 @@ 83C35E1E2B10923C00D8DD5C /* PlaybackCell.swift in Sources */, FC0E80262B1A0BBB00EF56D6 /* UploadPostRouter.swift in Sources */, FC2511A42B045D6C004717BC /* SignUpModels.swift in Sources */, + 8321A2FD2B1E4260000A12AF /* ReportEndPointFactory.swift in Sources */, FC767F932B1220CC0088CF9B /* NicknameDTO.swift in Sources */, FC3752312B170A620000D439 /* EditVideoWorker.swift in Sources */, 19A169272B176C5F00DB34C0 /* TagPlayListViewController.swift in Sources */, @@ -1188,6 +1195,7 @@ 836C33912B17629400ECAFB0 /* MapRouter.swift in Sources */, 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */, 836C339D2B1843BE00ECAFB0 /* SettingSceneViewController.swift in Sources */, + 8321A2FF2B1E428C000A12AF /* ReportDTO.swift in Sources */, FC9BB82C2B094E5500EB72A9 /* UICollectionViewLayout+.swift in Sources */, 194552232B0478B400299768 /* HomeRouter.swift in Sources */, 835783C32B14A41600E7D304 /* MockLoginWorker.swift in Sources */, diff --git a/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift b/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift new file mode 100644 index 0000000..d2d5dbf --- /dev/null +++ b/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift @@ -0,0 +1,15 @@ +// +// ReportDTO.swift +// Layover +// +// Created by 황지웅 on 12/5/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +struct ReportDTO: Codable { + let memberId: Int? + let boardId: Int + let reportType: String +} diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift new file mode 100644 index 0000000..a87aa1e --- /dev/null +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift @@ -0,0 +1,27 @@ +// +// ReportEndPointFactory.swift +// Layover +// +// Created by 황지웅 on 12/5/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +protocol ReportEndPointFactory { + func reportPlaybackVideoEndpoint(boardId: Int, reportType: String) -> EndPoint> +} + +struct DefaultReportEndPointFactory: ReportEndPointFactory { + func reportPlaybackVideoEndpoint(boardId: Int, reportType: String) -> EndPoint> { + var bodyParmeters: ReportDTO = ReportDTO( + memberId: nil, + boardId: boardId, + reportType: reportType) + + return EndPoint( + path: "/report", + method: .POST, + bodyParameters: bodyParmeters) + } +} From 7012538ec9fcc030a31c1d95c7baf21899186515 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 03:41:32 +0900 Subject: [PATCH 08/25] =?UTF-8?q?=E2=9C=A8=20UploadPost=EC=99=80=20EditTag?= =?UTF-8?q?=20=EA=B0=84=20tag=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=ED=8C=A8?= =?UTF-8?q?=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../down.imageset/Contents.json | 23 +++++++++++ .../down.imageset/leading-icon.png | Bin 0 -> 223 bytes .../down.imageset/leading-icon@2x.png | Bin 0 -> 317 bytes .../down.imageset/leading-icon@3x.png | Bin 0 -> 457 bytes .../Scenes/EditTag/EditTagInteractor.swift | 18 +++++++-- .../Scenes/EditTag/EditTagModels.swift | 18 +++++++++ .../Scenes/EditTag/EditTagPresenter.swift | 8 +++- .../Scenes/EditTag/EditTagRouter.swift | 11 ++--- .../EditTag/EditTagViewController.swift | 38 ++++++++++++++++-- .../EditVideo/EditVideoViewController.swift | 1 - .../UploadPost/UploadPostInteractor.swift | 8 ++++ .../Scenes/UploadPost/UploadPostModels.swift | 13 ++++++ .../UploadPost/UploadPostPresenter.swift | 5 +++ .../Scenes/UploadPost/UploadPostRouter.swift | 5 ++- .../UploadPost/UploadPostViewController.swift | 11 ++++- 15 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@3x.png diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json new file mode 100644 index 0000000..d801f8e --- /dev/null +++ b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "leading-icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "leading-icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "leading-icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..59c6763200980360e4881f2856aeff4bdd069f14 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#5>FS$kP61PlMnJXDDb%c*XVn|(er@g?*Bs^ zqDq~ejhB+XUYY*o+CFhU2FC|n#=$dryQkdVdq(l3_D;P+9+RIxTX4_mk#z7A#q}ya zaSQuOo|sq%xSuKVPKjLCxo4WUp!rULkCXY8_I+|}`M_FJd#pv_$5i2_z(9=+Gn-<7 P_A_|8`njxgN@xNAoNG>) literal 0 HcmV?d00001 diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0c3c1b030e59063d23f50853eb30f6c297b6ff14 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh= zN*g%2zu-7FF^SW~{!!5Gysv&$|8G6<^W+3-V_=ZsRhDSHU}xFwVO%4%Ribm;IZ_+r#+L7rjl>?EbUv>@_j=d+&U$OI-fT$NCAkJ-8pS-1KT+ z9x8gR9FbgncsGMOBP3iHuDsyqlHGsRXyb&FAQ4YjKbLh*2~7ZDptN%U literal 0 HcmV?d00001 diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift index 9344d61..2dfca70 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift @@ -9,14 +9,17 @@ import UIKit protocol EditTagBusinessLogic { - + func fetchTags() + func editTag(request: EditTagModels.EditTag.Request) } protocol EditTagDataStore { - + var tags: [String]? { get set } } -class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { +final class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { + + var tags: [String]? // MARK: - Properties @@ -24,4 +27,13 @@ class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { var presenter: EditTagPresentationLogic? + func fetchTags() { + guard let tags else { return } + presenter?.presentTags(with: EditTagModels.FetchTags.Response(tags: tags)) + } + + func editTag(request: EditTagModels.EditTag.Request) { + tags = request.tags + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift index 5f4d150..dd0781a 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift @@ -10,4 +10,22 @@ import UIKit enum EditTagModels { + enum FetchTags { + struct Request { + + } + struct Response { + let tags: [String] + } + struct ViewModel { + let tags: [String] + } + } + + enum EditTag { + struct Request { + let tags: [String] + } + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift index 1520638..ed6952a 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift @@ -9,14 +9,18 @@ import UIKit protocol EditTagPresentationLogic { - + func presentTags(with response: EditTagModels.FetchTags.Response) } -class EditTagPresenter: EditTagPresentationLogic { +final class EditTagPresenter: EditTagPresentationLogic { // MARK: - Properties typealias Models = EditTagModels weak var viewController: EditTagDisplayLogic? + func presentTags(with response: EditTagModels.FetchTags.Response) { + viewController?.displayTags(viewModel: EditTagModels.FetchTags.ViewModel(tags: response.tags)) + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift index 814ff8b..bb65454 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift @@ -21,16 +21,17 @@ class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing { // MARK: - Properties weak var viewController: EditTagViewController? + var dataStore: EditTagDataStore? // MARK: - Routing func routeToBack() { - let destination = viewController?.presentingViewController as? UploadPostViewController - var destinationDataStore = destination?.router?.dataStore - - // data passing - viewController?.navigationController?.popViewController(animated: true) + 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 + viewController?.dismiss(animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift index 2cf6c93..9d93cf3 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift @@ -9,13 +9,20 @@ import UIKit protocol EditTagDisplayLogic: AnyObject { - + func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) } -final class EditTagViewController: BaseViewController, EditTagDisplayLogic { +final class EditTagViewController: BaseViewController { // MARK: - UI Components + private lazy var closeButton: UIButton = { + let button: UIButton = UIButton() + button.setImage(UIImage.down, for: .normal) + button.addTarget(self, action: #selector(closeButtonDidTap), for: .touchUpInside) + return button + }() + private let tagTextField: LOTextField = { let textField: LOTextField = LOTextField() textField.placeholder = "태그" @@ -55,18 +62,22 @@ final class EditTagViewController: BaseViewController, EditTagDisplayLogic { override func viewDidLoad() { super.viewDidLoad() + interactor?.fetchTags() tagTextField.delegate = self } override func setConstraints() { super.setConstraints() - view.addSubviews(tagTextField, tagStackView) + view.addSubviews(closeButton, tagTextField, tagStackView) view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ - tagTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + closeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 21), + + tagTextField.topAnchor.constraint(equalTo: closeButton.bottomAnchor, constant: 8), tagTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), tagTextField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), tagTextField.heightAnchor.constraint(equalToConstant: 44), @@ -77,6 +88,15 @@ final class EditTagViewController: BaseViewController, EditTagDisplayLogic { ]) } + // 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)) + router?.routeToBack() + } + } // MARK: - UITextFieldDelegate @@ -89,3 +109,13 @@ extension EditTagViewController: UITextFieldDelegate { return true } } + + +extension EditTagViewController: EditTagDisplayLogic { + + func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) { + tagStackView.resetTagStackView() + viewModel.tags.forEach { tagStackView.addTag($0) } + } + +} diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index 3c95a59..0f9754c 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -80,7 +80,6 @@ final class EditVideoViewController: BaseViewController { override func viewWillDisappear(_ animated: Bool) { // interactor?.deleteVideo() - print("viewWillDisappear") } override func setConstraints() { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index e1baf08..1033f8b 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -12,6 +12,7 @@ import UIKit import OSLog protocol UploadPostBusinessLogic { + func fetchTags() func fetchThumbnailImage() func uploadPost() } @@ -19,6 +20,7 @@ protocol UploadPostBusinessLogic { protocol UploadPostDataStore { var videoURL: URL? { get set } var isMuted: Bool? { get set } + var tags: [String]? { get set } } final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { @@ -34,6 +36,12 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { var videoURL: URL? var isMuted: Bool? + var tags: [String]? = [] + + func fetchTags() { + guard let tags else { return } + presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags)) + } func fetchThumbnailImage() { guard let videoURL else { return } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index f729e49..1752cde 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,6 +10,19 @@ import UIKit enum UploadPostModels { + enum FetchTags { + struct Request { + + } + struct Response { + let tags: [String] + } + + struct ViewModel { + let tags: [String] + } + } + enum FetchThumbnail { struct Request { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 881d455..f1d1343 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -9,6 +9,7 @@ import UIKit protocol UploadPostPresentationLogic { + func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) } @@ -19,6 +20,10 @@ final class UploadPostPresenter: UploadPostPresentationLogic { typealias Models = UploadPostModels weak var viewController: UploadPostDisplayLogic? + func presentTags(with response: UploadPostModels.FetchTags.Response) { + viewController?.displayTags(viewModel: UploadPostModels.FetchTags.ViewModel(tags: response.tags)) + } + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) { let image = UIImage(cgImage: response.thumnailImage) viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift index 2edebfc..2d838ba 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift @@ -31,8 +31,9 @@ class UploadPostRouter: NSObject, UploadPostRoutingLogic, UploadPostDataPassing var destination = nextViewController.router?.dataStore else { return } - // Data Passing - viewController?.navigationController?.pushViewController(nextViewController, animated: true) + destination.tags = source.tags + nextViewController.modalPresentationStyle = .fullScreen + viewController?.present(nextViewController, animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 668cceb..3ebd10f 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -9,6 +9,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) } @@ -90,7 +91,7 @@ final class UploadPostViewController: BaseViewController { return textView }() - private let uploadButton: LOButton = { + private lazy var uploadButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) @@ -130,6 +131,10 @@ final class UploadPostViewController: BaseViewController { interactor?.fetchThumbnailImage() } + override func viewWillAppear(_ animated: Bool) { + interactor?.fetchTags() + } + override func setConstraints() { super.setConstraints() view.addSubviews(scrollView, uploadButton) @@ -233,6 +238,10 @@ final class UploadPostViewController: BaseViewController { } extension UploadPostViewController: UploadPostDisplayLogic { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) { + tagStackView.resetTagStackView() + viewModel.tags.forEach { tagStackView.addTag($0) } + } func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) { thumbnailImageView.image = viewModel.thumnailImage From 4966ac2754518aed1aa55c9761a8c8ab4aead775 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 04:17:43 +0900 Subject: [PATCH 09/25] =?UTF-8?q?=E2=9C=A8=20Post=20title=20textfield?= =?UTF-8?q?=EC=99=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/UploadPost/UploadPostInteractor.swift | 7 +++++++ .../Layover/Scenes/UploadPost/UploadPostModels.swift | 12 ++++++++++++ .../Scenes/UploadPost/UploadPostPresenter.swift | 6 ++++++ .../Scenes/UploadPost/UploadPostViewController.swift | 12 ++++++++++++ 4 files changed, 37 insertions(+) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 1033f8b..db6721d 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -14,6 +14,7 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func fetchThumbnailImage() + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) func uploadPost() } @@ -60,11 +61,17 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { } } + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) { + let response = UploadPostModels.CanUploadPost.Response(isEmpty: request.title == nil) + presenter?.presentUploadButton(with: response) + } + func uploadPost() { guard let isMuted else { return } if isMuted { extractVideoWithoutAudio() } + } private func extractVideoWithoutAudio() { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 1752cde..555cb54 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,6 +10,18 @@ import UIKit enum UploadPostModels { + enum CanUploadPost { + struct Request { + let title: String? + } + struct Response { + let isEmpty: Bool + } + struct ViewModel { + let canUpload: Bool + } + } + enum FetchTags { struct Request { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index f1d1343..72d1cc5 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostPresentationLogic { func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) } final class UploadPostPresenter: UploadPostPresentationLogic { @@ -29,4 +30,9 @@ final class UploadPostPresenter: UploadPostPresentationLogic { viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) } + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) { + let viewModel = UploadPostModels.CanUploadPost.ViewModel(canUpload: !response.isEmpty) + viewController?.displayUploadButton(viewModel: viewModel) + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 3ebd10f..8e8be8a 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) } final class UploadPostViewController: BaseViewController { @@ -43,6 +44,7 @@ final class UploadPostViewController: BaseViewController { private let titleTextField: LOTextField = { let textField = LOTextField() textField.placeholder = "제목" + textField.addTarget(self, action: #selector(titleTextChanged), for: .editingChanged) return textField }() @@ -95,6 +97,7 @@ final class UploadPostViewController: BaseViewController { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) + button.isEnabled = false return button }() @@ -223,6 +226,10 @@ final class UploadPostViewController: BaseViewController { scrollView.addGestureRecognizer(singleTapGestureRecognizer) } + @objc private func titleTextChanged() { + interactor?.canUploadPost(request: UploadPostModels.CanUploadPost.Request(title: titleTextField.text)) + } + @objc private func viewDidTap() { self.view.endEditing(true) } @@ -238,6 +245,7 @@ final class UploadPostViewController: BaseViewController { } extension UploadPostViewController: UploadPostDisplayLogic { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) { tagStackView.resetTagStackView() viewModel.tags.forEach { tagStackView.addTag($0) } @@ -247,4 +255,8 @@ extension UploadPostViewController: UploadPostDisplayLogic { thumbnailImageView.image = viewModel.thumnailImage } + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) { + uploadButton.isEnabled = viewModel.canUpload + } + } From dd1ba57e43395cbd2de25e5f7502a574204a5d9d Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 04:50:15 +0900 Subject: [PATCH 10/25] =?UTF-8?q?=E2=9C=A8=20UploadScene=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=9C=84=EB=8F=84=20=EA=B2=BD=EB=8F=84=EB=A1=9C=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EC=A3=BC=EC=86=8C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UploadPost/UploadPostInteractor.swift | 34 +++++++++++++++++++ .../Scenes/UploadPost/UploadPostModels.swift | 14 ++++++++ .../UploadPost/UploadPostPresenter.swift | 10 ++++++ .../UploadPost/UploadPostViewController.swift | 19 +++++++---- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index db6721d..4c93877 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -7,6 +7,7 @@ // import AVFoundation +import CoreLocation import UIKit import OSLog @@ -14,6 +15,7 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func fetchThumbnailImage() + func fetchCurrentAddress() func canUploadPost(request: UploadPostModels.CanUploadPost.Request) func uploadPost() } @@ -33,12 +35,18 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { lazy var worker = UploadPostWorker() var presenter: UploadPostPresentationLogic? + private let locationManager: CLLocationManager + // MARK: - Data Store var videoURL: URL? var isMuted: Bool? var tags: [String]? = [] + init(locationManager: CLLocationManager = CLLocationManager()) { + self.locationManager = locationManager + } + func fetchTags() { guard let tags else { return } presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags)) @@ -61,6 +69,32 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { } } + func fetchCurrentAddress() { + locationManager.desiredAccuracy = kCLLocationAccuracyBest + locationManager.startUpdatingLocation() + + guard let space = locationManager.location?.coordinate else { return } + let latitude = space.latitude + let longitude = space.longitude + let location = CLLocation(latitude: latitude, longitude: longitude) + let locale = Locale(identifier: "ko_KR") + + Task { + do { + let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last + let administrativeArea = address?.administrativeArea + let locality = address?.locality + let subLocality = address?.subLocality + let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea, + locality: locality, + subLocality: subLocality) + await MainActor.run { + presenter?.presentCurrentAddress(with: response) + } + } + } + } + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) { let response = UploadPostModels.CanUploadPost.Response(isEmpty: request.title == nil) presenter?.presentUploadButton(with: response) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 555cb54..246988b 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -47,6 +47,20 @@ enum UploadPostModels { } } + enum FetchCurrentAddress { + struct Request { + + } + struct Response { + let administrativeArea: String? + let locality: String? + let subLocality: String? + } + struct ViewModel { + let fullAddress: String + } + } + enum UploadPost { struct Request { let title: String diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 72d1cc5..20c86fa 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostPresentationLogic { func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) + func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) } @@ -30,6 +31,15 @@ final class UploadPostPresenter: UploadPostPresentationLogic { viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) } + func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) { + let addressSet = Set([response.administrativeArea, response.locality, response.subLocality]) + let fullAddress: String = Array(addressSet) + .compactMap { $0 } + .joined(separator: " ") + let viewModel = UploadPostModels.FetchCurrentAddress.ViewModel(fullAddress: fullAddress) + viewController?.displayCurrentAddress(viewModel: viewModel) + } + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) { let viewModel = UploadPostModels.CanUploadPost.ViewModel(canUpload: !response.isEmpty) viewController?.displayUploadButton(viewModel: viewModel) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 8e8be8a..4f8a972 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) + func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel) func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) } @@ -73,10 +74,9 @@ final class UploadPostViewController: BaseViewController { return imageLabel }() - private let locationLabel: UILabel = { + private let currentAddressLabel: UILabel = { let label = UILabel() label.font = .loFont(type: .body2) - label.text = "대구시 달서구 유천동" return label }() @@ -132,6 +132,7 @@ final class UploadPostViewController: BaseViewController { setConstraints() addTarget() interactor?.fetchThumbnailImage() + interactor?.fetchCurrentAddress() } override func viewWillAppear(_ animated: Bool) { @@ -169,7 +170,7 @@ final class UploadPostViewController: BaseViewController { private func setContentViewSubviewsConstraints() { contentView.addSubviews(thumbnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, - locationImageLabel, locationLabel, contentImageLabel, contentTextView) + locationImageLabel, currentAddressLabel, contentImageLabel, contentTextView) contentView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } @@ -205,13 +206,13 @@ final class UploadPostViewController: BaseViewController { locationImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), locationImageLabel.heightAnchor.constraint(equalToConstant: 22), - locationLabel.centerYAnchor.constraint(equalTo: locationImageLabel.centerYAnchor), - locationLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - locationLabel.leadingAnchor.constraint(equalTo: locationImageLabel.trailingAnchor), + currentAddressLabel.centerYAnchor.constraint(equalTo: locationImageLabel.centerYAnchor), + 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: locationLabel.leadingAnchor), + contentImageLabel.trailingAnchor.constraint(equalTo: currentAddressLabel.leadingAnchor), contentImageLabel.heightAnchor.constraint(equalToConstant: 22), contentTextView.topAnchor.constraint(equalTo: contentImageLabel.bottomAnchor, constant: 10), @@ -255,6 +256,10 @@ extension UploadPostViewController: UploadPostDisplayLogic { thumbnailImageView.image = viewModel.thumnailImage } + func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel) { + currentAddressLabel.text = viewModel.fullAddress + } + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) { uploadButton.isEnabled = viewModel.canUpload } From 1c6e286ee77896f03eea4a1f795b7332125314eb Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 00:27:23 +0900 Subject: [PATCH 11/25] =?UTF-8?q?=E2=9C=A8=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20vip=20=ED=94=8C=EB=A1=9C=EC=9A=B0,=20?= =?UTF-8?q?=EC=98=81=EC=83=81=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C,=20=EC=98=A4=EB=94=94=EC=98=A4=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditVideo/EditVideoInteractor.swift | 9 +++ .../Scenes/EditVideo/EditVideoModels.swift | 6 ++ .../Scenes/EditVideo/EditVideoRouter.swift | 11 ++- .../EditVideo/EditVideoViewController.swift | 14 +++- .../UploadPost/UploadPostInteractor.swift | 73 ++++++++++++++++++- .../Scenes/UploadPost/UploadPostModels.swift | 29 +++++++- .../UploadPost/UploadPostPresenter.swift | 9 ++- .../UploadPost/UploadPostViewController.swift | 32 +++++--- 8 files changed, 164 insertions(+), 19 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift index 814695e..0709dab 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoInteractor.swift @@ -12,10 +12,12 @@ import UIKit protocol EditVideoBusinessLogic { func fetchVideo(request: EditVideoModels.FetchVideo.Request) func deleteVideo() + func didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request) } protocol EditVideoDataStore { var videoURL: URL? { get set } + var isMuted: Bool? { get set } } final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { @@ -27,7 +29,10 @@ final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { var videoFileWorker: VideoFileWorker? var presenter: EditVideoPresentationLogic? + // MARK: - Data Store + var videoURL: URL? + var isMuted: Bool? func fetchVideo(request: EditVideoModels.FetchVideo.Request) { let isEdited = request.editedVideoURL != nil @@ -51,4 +56,8 @@ final class EditVideoInteractor: EditVideoBusinessLogic, EditVideoDataStore { videoFileWorker?.delete(at: videoURL) } + func didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request) { + isMuted = request.isMuted + } + } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift index 3d65206..7160780 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoModels.swift @@ -29,4 +29,10 @@ enum EditVideoModels { } } + enum DidFinishViedoEditing { + struct Request { + let isMuted: Bool + } + } + } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift index 74855e0..b7dcf75 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoRouter.swift @@ -26,7 +26,16 @@ final class EditVideoRouter: NSObject, EditVideoRoutingLogic, EditVideoDataPassi // MARK: - Routing func routeToNext() { - + let nextViewController = UploadPostViewController() + guard let source = dataStore, + var destination = nextViewController.router?.dataStore + else { return } + + // Data Passing + destination.videoURL = source.videoURL + destination.isMuted = source.isMuted + nextViewController.hidesBottomBarWhenPushed = true + viewController?.navigationController?.pushViewController(nextViewController, animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index d893c44..3c95a59 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -37,6 +37,7 @@ final class EditVideoViewController: BaseViewController { private let nextButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("다음", for: .normal) + button.addTarget(self, action: #selector(nextButtonDidTap), for: .touchUpInside) return button }() @@ -73,8 +74,13 @@ final class EditVideoViewController: BaseViewController { interactor?.fetchVideo(request: Models.FetchVideo.Request(editedVideoURL: nil)) } + override func viewWillAppear(_ animated: Bool) { + loopingPlayerView.play() + } + override func viewWillDisappear(_ animated: Bool) { - interactor?.deleteVideo() +// interactor?.deleteVideo() + print("viewWillDisappear") } override func setConstraints() { @@ -121,6 +127,12 @@ final class EditVideoViewController: BaseViewController { } } + @objc private func nextButtonDidTap() { + loopingPlayerView.pause() + interactor?.didFinishVideoEditing(request: EditVideoModels.DidFinishViedoEditing.Request(isMuted: soundButton.isSelected)) + router?.routeToNext() + } + } extension EditVideoViewController: UINavigationControllerDelegate, UIVideoEditorControllerDelegate { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 4b638dd..e1baf08 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -6,17 +6,22 @@ // Copyright © 2023 CodeBomber. All rights reserved. // +import AVFoundation import UIKit -protocol UploadPostBusinessLogic { +import OSLog +protocol UploadPostBusinessLogic { + func fetchThumbnailImage() + func uploadPost() } protocol UploadPostDataStore { - + var videoURL: URL? { get set } + var isMuted: Bool? { get set } } -class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { +final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { // MARK: - Properties @@ -25,4 +30,66 @@ class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { lazy var worker = UploadPostWorker() var presenter: UploadPostPresentationLogic? + // MARK: - Data Store + + var videoURL: URL? + var isMuted: Bool? + + func fetchThumbnailImage() { + guard let videoURL else { return } + let asset = AVAsset(url: videoURL) + let generator = AVAssetImageGenerator(asset: asset) + generator.appliesPreferredTrackTransform = true + Task { + do { + let image = try await generator.image(at: .zero).image + await MainActor.run { + presenter?.presentThumnailImage(with: UploadPostModels.FetchThumbnail.Response(thumnailImage: image)) + } + } catch let error { + os_log(.error, log: .default, "Failed to fetch ThumbnailImage with error: %@", error.localizedDescription) + } + } + } + + func uploadPost() { + guard let isMuted else { return } + if isMuted { + extractVideoWithoutAudio() + } + } + + private func extractVideoWithoutAudio() { + guard let videoURL else { return } + + let composition = AVMutableComposition() + let sourceAsset = AVURLAsset(url: videoURL) + guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, + preferredTrackID: kCMPersistentTrackID_Invalid) else { return } + + Task { + do { + let sourceAssetduration = try await sourceAsset.load(.duration) + let sourceVideoTrack = try await sourceAsset.load(.tracks)[0] + compositionVideoTrack.preferredTransform = try await sourceVideoTrack.load(.preferredTransform) + + let timeRange: CMTimeRange = CMTimeRangeMake(start: .zero, duration: sourceAssetduration) + try compositionVideoTrack.insertTimeRange(timeRange, + of: sourceVideoTrack, + at: .zero) + + if FileManager.default.fileExists(atPath: videoURL.path()) { + try FileManager.default.removeItem(at: videoURL) + } + + let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) + exporter?.outputURL = videoURL + exporter?.outputFileType = AVFileType.mov + await exporter?.export() + } catch let error { + os_log(.error, log: .default, "Failed to fetch extractVideoWithoutAudio with error: %@", error.localizedDescription) + } + } + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 6d904f4..f729e49 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,5 +10,32 @@ import UIKit enum UploadPostModels { - // MARK: - Use Cases + enum FetchThumbnail { + struct Request { + + } + struct Response { + let thumnailImage: CGImage + } + struct ViewModel { + let thumnailImage: UIImage + } + } + + enum UploadPost { + struct Request { + let title: String + let content: String? + let latitude: Double + let longitude: Double + let tags: [String] + } + struct Response { + + } + struct VideModel { + + } + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 1e7dce2..881d455 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -9,14 +9,19 @@ import UIKit protocol UploadPostPresentationLogic { - + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) } -class UploadPostPresenter: UploadPostPresentationLogic { +final class UploadPostPresenter: UploadPostPresentationLogic { // MARK: - Properties typealias Models = UploadPostModels weak var viewController: UploadPostDisplayLogic? + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) { + let image = UIImage(cgImage: response.thumnailImage) + viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 1e76e90..668cceb 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -9,10 +9,10 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { - + func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) } -final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic { +final class UploadPostViewController: BaseViewController { // MARK: - UI Components @@ -24,7 +24,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic private let contentView: UIView = UIView() - private let thumnailImageView: UIImageView = { + private let thumbnailImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true @@ -93,6 +93,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic private let uploadButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) + button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) return button }() @@ -126,6 +127,7 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic super.viewDidLoad() setConstraints() addTarget() + interactor?.fetchThumbnailImage() } override func setConstraints() { @@ -158,18 +160,18 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic } private func setContentViewSubviewsConstraints() { - contentView.addSubviews(thumnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, + contentView.addSubviews(thumbnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, locationImageLabel, locationLabel, contentImageLabel, contentTextView) contentView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ - thumnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), - thumnailImageView.widthAnchor.constraint(equalToConstant: 156), - thumnailImageView.heightAnchor.constraint(equalToConstant: 251), - thumnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + thumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), + thumbnailImageView.widthAnchor.constraint(equalToConstant: 156), + thumbnailImageView.heightAnchor.constraint(equalToConstant: 251), + thumbnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - titleImageLabel.topAnchor.constraint(equalTo: thumnailImageView.bottomAnchor, constant: 22), + titleImageLabel.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 22), titleImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleImageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleImageLabel.heightAnchor.constraint(equalToConstant: 22), @@ -224,8 +226,16 @@ final class UploadPostViewController: BaseViewController, UploadPostDisplayLogic router?.routeToNext() } + @objc private func uploadButtonDidTap() { + interactor?.uploadPost() + } + } -#Preview { - UploadPostViewController() +extension UploadPostViewController: UploadPostDisplayLogic { + + func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) { + thumbnailImageView.image = viewModel.thumnailImage + } + } From 462a1759e67a6467082d6d396b65b42c69986e2c Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 03:41:32 +0900 Subject: [PATCH 12/25] =?UTF-8?q?=E2=9C=A8=20UploadPost=EC=99=80=20EditTag?= =?UTF-8?q?=20=EA=B0=84=20tag=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=ED=8C=A8?= =?UTF-8?q?=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../down.imageset/Contents.json | 23 +++++++++++ .../down.imageset/leading-icon.png | Bin 0 -> 223 bytes .../down.imageset/leading-icon@2x.png | Bin 0 -> 317 bytes .../down.imageset/leading-icon@3x.png | Bin 0 -> 457 bytes .../Scenes/EditTag/EditTagInteractor.swift | 18 +++++++-- .../Scenes/EditTag/EditTagModels.swift | 18 +++++++++ .../Scenes/EditTag/EditTagPresenter.swift | 8 +++- .../Scenes/EditTag/EditTagRouter.swift | 11 ++--- .../EditTag/EditTagViewController.swift | 38 ++++++++++++++++-- .../EditVideo/EditVideoViewController.swift | 1 - .../UploadPost/UploadPostInteractor.swift | 8 ++++ .../Scenes/UploadPost/UploadPostModels.swift | 13 ++++++ .../UploadPost/UploadPostPresenter.swift | 5 +++ .../Scenes/UploadPost/UploadPostRouter.swift | 5 ++- .../UploadPost/UploadPostViewController.swift | 11 ++++- 15 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png create mode 100644 iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@3x.png diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json new file mode 100644 index 0000000..d801f8e --- /dev/null +++ b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "leading-icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "leading-icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "leading-icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..59c6763200980360e4881f2856aeff4bdd069f14 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#5>FS$kP61PlMnJXDDb%c*XVn|(er@g?*Bs^ zqDq~ejhB+XUYY*o+CFhU2FC|n#=$dryQkdVdq(l3_D;P+9+RIxTX4_mk#z7A#q}ya zaSQuOo|sq%xSuKVPKjLCxo4WUp!rULkCXY8_I+|}`M_FJd#pv_$5i2_z(9=+Gn-<7 P_A_|8`njxgN@xNAoNG>) literal 0 HcmV?d00001 diff --git a/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png b/iOS/Layover/Layover/Resources/Assets.xcassets/down.imageset/leading-icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0c3c1b030e59063d23f50853eb30f6c297b6ff14 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh= zN*g%2zu-7FF^SW~{!!5Gysv&$|8G6<^W+3-V_=ZsRhDSHU}xFwVO%4%Ribm;IZ_+r#+L7rjl>?EbUv>@_j=d+&U$OI-fT$NCAkJ-8pS-1KT+ z9x8gR9FbgncsGMOBP3iHuDsyqlHGsRXyb&FAQ4YjKbLh*2~7ZDptN%U literal 0 HcmV?d00001 diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift index 9344d61..2dfca70 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagInteractor.swift @@ -9,14 +9,17 @@ import UIKit protocol EditTagBusinessLogic { - + func fetchTags() + func editTag(request: EditTagModels.EditTag.Request) } protocol EditTagDataStore { - + var tags: [String]? { get set } } -class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { +final class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { + + var tags: [String]? // MARK: - Properties @@ -24,4 +27,13 @@ class EditTagInteractor: EditTagBusinessLogic, EditTagDataStore { var presenter: EditTagPresentationLogic? + func fetchTags() { + guard let tags else { return } + presenter?.presentTags(with: EditTagModels.FetchTags.Response(tags: tags)) + } + + func editTag(request: EditTagModels.EditTag.Request) { + tags = request.tags + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift index 5f4d150..dd0781a 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagModels.swift @@ -10,4 +10,22 @@ import UIKit enum EditTagModels { + enum FetchTags { + struct Request { + + } + struct Response { + let tags: [String] + } + struct ViewModel { + let tags: [String] + } + } + + enum EditTag { + struct Request { + let tags: [String] + } + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift index 1520638..ed6952a 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagPresenter.swift @@ -9,14 +9,18 @@ import UIKit protocol EditTagPresentationLogic { - + func presentTags(with response: EditTagModels.FetchTags.Response) } -class EditTagPresenter: EditTagPresentationLogic { +final class EditTagPresenter: EditTagPresentationLogic { // MARK: - Properties typealias Models = EditTagModels weak var viewController: EditTagDisplayLogic? + func presentTags(with response: EditTagModels.FetchTags.Response) { + viewController?.displayTags(viewModel: EditTagModels.FetchTags.ViewModel(tags: response.tags)) + } + } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift index 814ff8b..bb65454 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift @@ -21,16 +21,17 @@ class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing { // MARK: - Properties weak var viewController: EditTagViewController? + var dataStore: EditTagDataStore? // MARK: - Routing func routeToBack() { - let destination = viewController?.presentingViewController as? UploadPostViewController - var destinationDataStore = destination?.router?.dataStore - - // data passing - viewController?.navigationController?.popViewController(animated: true) + 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 + viewController?.dismiss(animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift index 2cf6c93..9d93cf3 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift @@ -9,13 +9,20 @@ import UIKit protocol EditTagDisplayLogic: AnyObject { - + func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) } -final class EditTagViewController: BaseViewController, EditTagDisplayLogic { +final class EditTagViewController: BaseViewController { // MARK: - UI Components + private lazy var closeButton: UIButton = { + let button: UIButton = UIButton() + button.setImage(UIImage.down, for: .normal) + button.addTarget(self, action: #selector(closeButtonDidTap), for: .touchUpInside) + return button + }() + private let tagTextField: LOTextField = { let textField: LOTextField = LOTextField() textField.placeholder = "태그" @@ -55,18 +62,22 @@ final class EditTagViewController: BaseViewController, EditTagDisplayLogic { override func viewDidLoad() { super.viewDidLoad() + interactor?.fetchTags() tagTextField.delegate = self } override func setConstraints() { super.setConstraints() - view.addSubviews(tagTextField, tagStackView) + view.addSubviews(closeButton, tagTextField, tagStackView) view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ - tagTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + closeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 21), + + tagTextField.topAnchor.constraint(equalTo: closeButton.bottomAnchor, constant: 8), tagTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), tagTextField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), tagTextField.heightAnchor.constraint(equalToConstant: 44), @@ -77,6 +88,15 @@ final class EditTagViewController: BaseViewController, EditTagDisplayLogic { ]) } + // 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)) + router?.routeToBack() + } + } // MARK: - UITextFieldDelegate @@ -89,3 +109,13 @@ extension EditTagViewController: UITextFieldDelegate { return true } } + + +extension EditTagViewController: EditTagDisplayLogic { + + func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) { + tagStackView.resetTagStackView() + viewModel.tags.forEach { tagStackView.addTag($0) } + } + +} diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index 3c95a59..0f9754c 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -80,7 +80,6 @@ final class EditVideoViewController: BaseViewController { override func viewWillDisappear(_ animated: Bool) { // interactor?.deleteVideo() - print("viewWillDisappear") } override func setConstraints() { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index e1baf08..1033f8b 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -12,6 +12,7 @@ import UIKit import OSLog protocol UploadPostBusinessLogic { + func fetchTags() func fetchThumbnailImage() func uploadPost() } @@ -19,6 +20,7 @@ protocol UploadPostBusinessLogic { protocol UploadPostDataStore { var videoURL: URL? { get set } var isMuted: Bool? { get set } + var tags: [String]? { get set } } final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { @@ -34,6 +36,12 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { var videoURL: URL? var isMuted: Bool? + var tags: [String]? = [] + + func fetchTags() { + guard let tags else { return } + presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags)) + } func fetchThumbnailImage() { guard let videoURL else { return } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index f729e49..1752cde 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,6 +10,19 @@ import UIKit enum UploadPostModels { + enum FetchTags { + struct Request { + + } + struct Response { + let tags: [String] + } + + struct ViewModel { + let tags: [String] + } + } + enum FetchThumbnail { struct Request { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 881d455..f1d1343 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -9,6 +9,7 @@ import UIKit protocol UploadPostPresentationLogic { + func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) } @@ -19,6 +20,10 @@ final class UploadPostPresenter: UploadPostPresentationLogic { typealias Models = UploadPostModels weak var viewController: UploadPostDisplayLogic? + func presentTags(with response: UploadPostModels.FetchTags.Response) { + viewController?.displayTags(viewModel: UploadPostModels.FetchTags.ViewModel(tags: response.tags)) + } + func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) { let image = UIImage(cgImage: response.thumnailImage) viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift index 2edebfc..2d838ba 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift @@ -31,8 +31,9 @@ class UploadPostRouter: NSObject, UploadPostRoutingLogic, UploadPostDataPassing var destination = nextViewController.router?.dataStore else { return } - // Data Passing - viewController?.navigationController?.pushViewController(nextViewController, animated: true) + destination.tags = source.tags + nextViewController.modalPresentationStyle = .fullScreen + viewController?.present(nextViewController, animated: true) } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 668cceb..3ebd10f 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -9,6 +9,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) } @@ -90,7 +91,7 @@ final class UploadPostViewController: BaseViewController { return textView }() - private let uploadButton: LOButton = { + private lazy var uploadButton: LOButton = { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) @@ -130,6 +131,10 @@ final class UploadPostViewController: BaseViewController { interactor?.fetchThumbnailImage() } + override func viewWillAppear(_ animated: Bool) { + interactor?.fetchTags() + } + override func setConstraints() { super.setConstraints() view.addSubviews(scrollView, uploadButton) @@ -233,6 +238,10 @@ final class UploadPostViewController: BaseViewController { } extension UploadPostViewController: UploadPostDisplayLogic { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) { + tagStackView.resetTagStackView() + viewModel.tags.forEach { tagStackView.addTag($0) } + } func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) { thumbnailImageView.image = viewModel.thumnailImage From 3cf9bf028b9c62ff7597a356d0bc70d8fa28d527 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 04:17:43 +0900 Subject: [PATCH 13/25] =?UTF-8?q?=E2=9C=A8=20Post=20title=20textfield?= =?UTF-8?q?=EC=99=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/UploadPost/UploadPostInteractor.swift | 7 +++++++ .../Layover/Scenes/UploadPost/UploadPostModels.swift | 12 ++++++++++++ .../Scenes/UploadPost/UploadPostPresenter.swift | 6 ++++++ .../Scenes/UploadPost/UploadPostViewController.swift | 12 ++++++++++++ 4 files changed, 37 insertions(+) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 1033f8b..db6721d 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -14,6 +14,7 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func fetchThumbnailImage() + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) func uploadPost() } @@ -60,11 +61,17 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { } } + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) { + let response = UploadPostModels.CanUploadPost.Response(isEmpty: request.title == nil) + presenter?.presentUploadButton(with: response) + } + func uploadPost() { guard let isMuted else { return } if isMuted { extractVideoWithoutAudio() } + } private func extractVideoWithoutAudio() { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 1752cde..555cb54 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -10,6 +10,18 @@ import UIKit enum UploadPostModels { + enum CanUploadPost { + struct Request { + let title: String? + } + struct Response { + let isEmpty: Bool + } + struct ViewModel { + let canUpload: Bool + } + } + enum FetchTags { struct Request { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index f1d1343..72d1cc5 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostPresentationLogic { func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) } final class UploadPostPresenter: UploadPostPresentationLogic { @@ -29,4 +30,9 @@ final class UploadPostPresenter: UploadPostPresentationLogic { viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) } + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) { + let viewModel = UploadPostModels.CanUploadPost.ViewModel(canUpload: !response.isEmpty) + viewController?.displayUploadButton(viewModel: viewModel) + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 3ebd10f..8e8be8a 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) } final class UploadPostViewController: BaseViewController { @@ -43,6 +44,7 @@ final class UploadPostViewController: BaseViewController { private let titleTextField: LOTextField = { let textField = LOTextField() textField.placeholder = "제목" + textField.addTarget(self, action: #selector(titleTextChanged), for: .editingChanged) return textField }() @@ -95,6 +97,7 @@ final class UploadPostViewController: BaseViewController { let button = LOButton(style: .basic) button.setTitle("업로드", for: .normal) button.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) + button.isEnabled = false return button }() @@ -223,6 +226,10 @@ final class UploadPostViewController: BaseViewController { scrollView.addGestureRecognizer(singleTapGestureRecognizer) } + @objc private func titleTextChanged() { + interactor?.canUploadPost(request: UploadPostModels.CanUploadPost.Request(title: titleTextField.text)) + } + @objc private func viewDidTap() { self.view.endEditing(true) } @@ -238,6 +245,7 @@ final class UploadPostViewController: BaseViewController { } extension UploadPostViewController: UploadPostDisplayLogic { + func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) { tagStackView.resetTagStackView() viewModel.tags.forEach { tagStackView.addTag($0) } @@ -247,4 +255,8 @@ extension UploadPostViewController: UploadPostDisplayLogic { thumbnailImageView.image = viewModel.thumnailImage } + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) { + uploadButton.isEnabled = viewModel.canUpload + } + } From bd2a1d503e3c3ae826b2bed64331e7475f3007bb Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 04:50:15 +0900 Subject: [PATCH 14/25] =?UTF-8?q?=E2=9C=A8=20UploadScene=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=9C=84=EB=8F=84=20=EA=B2=BD=EB=8F=84=EB=A1=9C=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EC=A3=BC=EC=86=8C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UploadPost/UploadPostInteractor.swift | 34 +++++++++++++++++++ .../Scenes/UploadPost/UploadPostModels.swift | 14 ++++++++ .../UploadPost/UploadPostPresenter.swift | 10 ++++++ .../UploadPost/UploadPostViewController.swift | 19 +++++++---- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index db6721d..4c93877 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -7,6 +7,7 @@ // import AVFoundation +import CoreLocation import UIKit import OSLog @@ -14,6 +15,7 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func fetchThumbnailImage() + func fetchCurrentAddress() func canUploadPost(request: UploadPostModels.CanUploadPost.Request) func uploadPost() } @@ -33,12 +35,18 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { lazy var worker = UploadPostWorker() var presenter: UploadPostPresentationLogic? + private let locationManager: CLLocationManager + // MARK: - Data Store var videoURL: URL? var isMuted: Bool? var tags: [String]? = [] + init(locationManager: CLLocationManager = CLLocationManager()) { + self.locationManager = locationManager + } + func fetchTags() { guard let tags else { return } presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags)) @@ -61,6 +69,32 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { } } + func fetchCurrentAddress() { + locationManager.desiredAccuracy = kCLLocationAccuracyBest + locationManager.startUpdatingLocation() + + guard let space = locationManager.location?.coordinate else { return } + let latitude = space.latitude + let longitude = space.longitude + let location = CLLocation(latitude: latitude, longitude: longitude) + let locale = Locale(identifier: "ko_KR") + + Task { + do { + let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last + let administrativeArea = address?.administrativeArea + let locality = address?.locality + let subLocality = address?.subLocality + let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea, + locality: locality, + subLocality: subLocality) + await MainActor.run { + presenter?.presentCurrentAddress(with: response) + } + } + } + } + func canUploadPost(request: UploadPostModels.CanUploadPost.Request) { let response = UploadPostModels.CanUploadPost.Response(isEmpty: request.title == nil) presenter?.presentUploadButton(with: response) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 555cb54..246988b 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -47,6 +47,20 @@ enum UploadPostModels { } } + enum FetchCurrentAddress { + struct Request { + + } + struct Response { + let administrativeArea: String? + let locality: String? + let subLocality: String? + } + struct ViewModel { + let fullAddress: String + } + } + enum UploadPost { struct Request { let title: String diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 72d1cc5..20c86fa 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostPresentationLogic { func presentTags(with response: UploadPostModels.FetchTags.Response) func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) + func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) } @@ -30,6 +31,15 @@ final class UploadPostPresenter: UploadPostPresentationLogic { viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) } + func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) { + let addressSet = Set([response.administrativeArea, response.locality, response.subLocality]) + let fullAddress: String = Array(addressSet) + .compactMap { $0 } + .joined(separator: " ") + let viewModel = UploadPostModels.FetchCurrentAddress.ViewModel(fullAddress: fullAddress) + viewController?.displayCurrentAddress(viewModel: viewModel) + } + func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) { let viewModel = UploadPostModels.CanUploadPost.ViewModel(canUpload: !response.isEmpty) viewController?.displayUploadButton(viewModel: viewModel) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 8e8be8a..4f8a972 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -11,6 +11,7 @@ import UIKit protocol UploadPostDisplayLogic: AnyObject { func displayTags(viewModel: UploadPostModels.FetchTags.ViewModel) func displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel) + func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel) func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) } @@ -73,10 +74,9 @@ final class UploadPostViewController: BaseViewController { return imageLabel }() - private let locationLabel: UILabel = { + private let currentAddressLabel: UILabel = { let label = UILabel() label.font = .loFont(type: .body2) - label.text = "대구시 달서구 유천동" return label }() @@ -132,6 +132,7 @@ final class UploadPostViewController: BaseViewController { setConstraints() addTarget() interactor?.fetchThumbnailImage() + interactor?.fetchCurrentAddress() } override func viewWillAppear(_ animated: Bool) { @@ -169,7 +170,7 @@ final class UploadPostViewController: BaseViewController { private func setContentViewSubviewsConstraints() { contentView.addSubviews(thumbnailImageView, titleImageLabel, titleTextField, tagImageLabel, tagStackView, addTagButton, - locationImageLabel, locationLabel, contentImageLabel, contentTextView) + locationImageLabel, currentAddressLabel, contentImageLabel, contentTextView) contentView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } @@ -205,13 +206,13 @@ final class UploadPostViewController: BaseViewController { locationImageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), locationImageLabel.heightAnchor.constraint(equalToConstant: 22), - locationLabel.centerYAnchor.constraint(equalTo: locationImageLabel.centerYAnchor), - locationLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - locationLabel.leadingAnchor.constraint(equalTo: locationImageLabel.trailingAnchor), + currentAddressLabel.centerYAnchor.constraint(equalTo: locationImageLabel.centerYAnchor), + 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: locationLabel.leadingAnchor), + contentImageLabel.trailingAnchor.constraint(equalTo: currentAddressLabel.leadingAnchor), contentImageLabel.heightAnchor.constraint(equalToConstant: 22), contentTextView.topAnchor.constraint(equalTo: contentImageLabel.bottomAnchor, constant: 10), @@ -255,6 +256,10 @@ extension UploadPostViewController: UploadPostDisplayLogic { thumbnailImageView.image = viewModel.thumnailImage } + func displayCurrentAddress(viewModel: UploadPostModels.FetchCurrentAddress.ViewModel) { + currentAddressLabel.text = viewModel.fullAddress + } + func displayUploadButton(viewModel: UploadPostModels.CanUploadPost.ViewModel) { uploadButton.isEnabled = viewModel.canUpload } From 48e792f20466a720ab8b421be0c846a308141559 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:25:46 +0900 Subject: [PATCH 15/25] =?UTF-8?q?:sparkles:=20=EC=8B=A0=EA=B3=A0=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/DesignSystem/LOPopUpView.swift | 4 +++ .../DesignSystem/LOReportStackView.swift | 4 +++ .../Scenes/Playback/Cell/PlaybackCell.swift | 2 ++ .../Scenes/Playback/PlaybackModels.swift | 17 ++++++++++ .../Scenes/Playback/PlaybackPresenter.swift | 1 + .../Scenes/Playback/PlaybackRouter.swift | 12 +++++-- .../Playback/PlaybackViewController.swift | 7 ++--- .../Scenes/Report/ReportInteractor.swift | 14 +++++++++ .../Layover/Scenes/Report/ReportModels.swift | 14 +++++++++ .../Scenes/Report/ReportPresenter.swift | 10 ++++++ .../Scenes/Report/ReportViewController.swift | 29 ++++++++++++++--- .../Layover/Scenes/Report/ReportWorker.swift | 31 ++++++++++++++++++- 12 files changed, 132 insertions(+), 13 deletions(-) diff --git a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift index 0341074..ffc61fd 100644 --- a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift @@ -42,6 +42,10 @@ final class LOPopUpView: UIView { setConstraints() } + func getReportContent() -> String { + reportStackView.reportContent + } + private func setConstraints() { addSubviews(titleLabel, reportStackView, cancelButton, reportButton) subviews.forEach { subView in diff --git a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift index aac6bcc..daa7676 100644 --- a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift @@ -20,6 +20,8 @@ final class LOReportStackView: UIStackView { } }() + var reportContent: String = "스팸 홍보 / 도배글이에요" + override init(frame: CGRect) { super.init(frame: frame) setUI() @@ -54,6 +56,8 @@ final class LOReportStackView: UIStackView { self.reportViews.forEach { reportView in if reportView.index == tempView.index { reportView.onRadio() + guard let reportContentStr: String = reportView.contentLabel.text else { return } + reportContent = reportContentStr } else { reportView.offRadio() } diff --git a/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift b/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift index 72d2239..ba9c0ce 100644 --- a/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift +++ b/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift @@ -10,6 +10,7 @@ import UIKit final class PlaybackCell: UICollectionViewCell { let playbackView: PlaybackView = PlaybackView() + var boardId: Int = 0 override init(frame: CGRect) { super.init(frame: frame) @@ -22,6 +23,7 @@ final class PlaybackCell: UICollectionViewCell { } func setPlaybackContents(info: PlaybackModels.PlaybackInfo) { + boardId = info.boardId playbackView.descriptionView.titleLabel.text = info.title playbackView.descriptionView.setText(info.content) playbackView.profileLabel.text = info.profileName diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift index 1dba042..958e975 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift @@ -21,6 +21,7 @@ enum PlaybackModels { } struct PlaybackInfo: Hashable { + let boardId: Int let title: String let content: String let profileImageURL: URL? @@ -127,4 +128,20 @@ enum PlaybackModels { let curCell: PlaybackCell } } + + // MARK: - UseCase Report Playback Video + + enum ReportPlaybackVideo { + struct Request { + + } + + struct Response { + let boardId: Int + } + + struct ViewModel { + let boardId: Int + } + } } diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift index f8a68ea..098b29b 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift @@ -35,6 +35,7 @@ final class PlaybackPresenter: PlaybackPresentationLogic { return nil } return Models.PlaybackVideo(playbackInfo: PlaybackModels.PlaybackInfo( + boardId: post.board.identifier, title: post.board.title, content: post.board.description ?? "", profileImageURL: post.member.profileImageURL, diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift index 2100869..d71af8b 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift @@ -9,7 +9,7 @@ import UIKit protocol PlaybackRoutingLogic { - func routeToNext() + func routeToReport() } protocol PlaybackDataPassing { @@ -25,7 +25,13 @@ final class PlaybackRouter: NSObject, PlaybackRoutingLogic, PlaybackDataPassing // MARK: - Routing - func routeToNext() { - + func routeToReport() { + let reportViewController: ReportViewController = ReportViewController() + guard let source = dataStore, + var destination = reportViewController.router?.dataStore + else { return } + destination.boardID = source.prevCell?.boardId + reportViewController.modalPresentationStyle = .fullScreen + viewController?.present(reportViewController, animated: false) } } diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift index d3128e4..3065965 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift @@ -177,8 +177,8 @@ final class PlaybackViewController: BaseViewController { @objc private func reportButtonDidTap() { let alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let reportAction: UIAlertAction = UIAlertAction(title: "신고", style: .destructive, handler: { - action in -// self.reportPlaybackVideo() + _ in + self.router?.routeToReport() }) let cancelAction: UIAlertAction = UIAlertAction(title: "취소", style: .cancel) alert.addAction(reportAction) @@ -210,9 +210,6 @@ extension PlaybackViewController: PlaybackDisplayLogic { curCell.playbackView.playPlayer() setPlayerSlider(at: curCell.playbackView) // Slider가 원점으로 돌아가는 시간 필요 -// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { -// self.playerSlider.isHidden = false -// } Task { await slowShowPlayerSlider() } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift index 3984cd8..1979cdb 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift @@ -9,9 +9,11 @@ import UIKit protocol ReportBusinessLogic { + func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) } protocol ReportDataStore { + var boardID: Int? { get set } } final class ReportInteractor: ReportBusinessLogic, ReportDataStore { @@ -23,4 +25,16 @@ final class ReportInteractor: ReportBusinessLogic, ReportDataStore { lazy var worker = ReportWorker() var presenter: ReportPresentationLogic? + var boardID: Int? + + func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) { + guard let boardID else { return } + Task { + let result: Bool = await worker.reportPlaybackVideo(boardId: boardID, reportContent: request.reportContent) + let response: Models.ReportPlaybackVideo.Response = Models.ReportPlaybackVideo.Response(reportResult: result) + await MainActor.run { + presenter?.presentReportPlaybackVideo(with: response) + } + } + } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportModels.swift b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift index 8a0cdc1..8a3c571 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportModels.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift @@ -11,4 +11,18 @@ import UIKit enum ReportModels { // MARK: - Use Cases + + enum ReportPlaybackVideo { + struct Request { + let reportContent: String + } + + struct Response { + let reportResult: Bool + } + + struct ViewModel { + let reportMessage: String + } + } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift index f798a0c..b5e5b40 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift @@ -9,6 +9,7 @@ import Foundation protocol ReportPresentationLogic { + func presentReportPlaybackVideo(with response: ReportModels.ReportPlaybackVideo.Response) } final class ReportPresenter: ReportPresentationLogic { @@ -18,4 +19,13 @@ final class ReportPresenter: ReportPresentationLogic { typealias Models = ReportModels weak var viewController: ReportDisplayLogic? + func presentReportPlaybackVideo(with response: ReportModels.ReportPlaybackVideo.Response) { + let viewModel: Models.ReportPlaybackVideo.ViewModel + if response.reportResult { + viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: "신고가 접수되었습니다.") + } else { + viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: "신고 접수에 실패했습니다. 다시 시도해주세요.") + } + viewController?.displayReportResult(viewModel: viewModel) + } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift index 6306d7e..8a30191 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift @@ -8,11 +8,15 @@ import UIKit -protocol ReportDisplayLogic: AnyObject { +protocol ReportViewControllerDelegate: AnyObject { + func reportPlaybackVideo(reportContent: String) +} +protocol ReportDisplayLogic: AnyObject { + func displayReportResult(viewModel: ReportModels.ReportPlaybackVideo.ViewModel) } -final class ReportViewController: BaseViewController, ReportDisplayLogic { +final class ReportViewController: BaseViewController { // MARK: - UI Components @@ -81,8 +85,25 @@ final class ReportViewController: BaseViewController, ReportDisplayLogic { popUpView.heightAnchor.constraint(equalToConstant: 450) ]) } + + @objc private func cancelButtonDidTap() { + self.dismiss(animated: true) + } } -#Preview { - ReportViewController() +extension ReportViewController: ReportViewControllerDelegate { + func reportPlaybackVideo(reportContent: String) { + let request: Models.ReportPlaybackVideo.Request = Models.ReportPlaybackVideo.Request(reportContent: reportContent) + interactor?.reportPlaybackVideo(with: request) + } } + +extension ReportViewController: ReportDisplayLogic { + func displayReportResult(viewModel: ReportModels.ReportPlaybackVideo.ViewModel) { + Toast.shared.showToast(message: viewModel.reportMessage) + } +} + +//#Preview { +// ReportViewController() +//} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift index 471687e..c206a9c 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift @@ -8,10 +8,39 @@ import Foundation -final class ReportWorker { +import OSLog + +protocol ReportWorkerProtocol { + func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool +} + +final class ReportWorker: ReportWorkerProtocol { // MARK: - Properties typealias Models = ReportModels + private let reportEndPointFactory: ReportEndPointFactory + private let provider: ProviderType + + init(reportEndPointFactory: ReportEndPointFactory = DefaultReportEndPointFactory(), provider: ProviderType = Provider()) { + self.reportEndPointFactory = reportEndPointFactory + self.provider = provider + } + + func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool { + let endPoint = reportEndPointFactory.reportPlaybackVideoEndpoint(boardId: boardId, reportType: reportContent) + do { + let responseData = try await provider.request(with: endPoint, authenticationIfNeeded: false) + print(responseData.message) + guard let _ = responseData.data else { + os_log(.error, log: .default, "Failed to sign up with error: %@", responseData.message) + return false + } + } catch { + os_log(.error, log: .default, "Failed to sign up with error: %@", error.localizedDescription) + return false + } + return true + } } From 6c59e0a30a8dcd8b2310a1deccd6b1bba2e5c411 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:23:50 +0900 Subject: [PATCH 16/25] =?UTF-8?q?:sparkles:=20=EC=8B=A0=EA=B3=A0=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20Mock=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 8 +++ .../Layover/DesignSystem/LOPopUpView.swift | 14 ++++++ .../Mock/MockData/ReportPlaybackVideo.json | 10 ++++ .../Scenes/Report/ReportConfigurator.swift | 3 +- .../Scenes/Report/ReportInteractor.swift | 6 ++- .../Scenes/Report/ReportViewController.swift | 10 +++- .../Layover/Scenes/Report/ReportWorker.swift | 7 ++- .../Workers/Mocks/MockReportWorker.swift | 50 +++++++++++++++++++ 8 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 iOS/Layover/Layover/Network/Mock/MockData/ReportPlaybackVideo.json create mode 100644 iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index f8cdac7..1d7bf39 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -77,6 +77,8 @@ 8321A2FB2B1E1739000A12AF /* LOReportContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */; }; 8321A2FD2B1E4260000A12AF /* ReportEndPointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FC2B1E4260000A12AF /* ReportEndPointFactory.swift */; }; 8321A2FF2B1E428C000A12AF /* ReportDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */; }; + 8321A3012B1F1EC5000A12AF /* MockReportWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8321A3002B1F1EC5000A12AF /* MockReportWorker.swift */; }; + 8321A3032B1F2041000A12AF /* ReportPlaybackVideo.json in Resources */ = {isa = PBXBuildFile; fileRef = 8321A3022B1F2041000A12AF /* ReportPlaybackVideo.json */; }; 834B7BD52B14F888002BD176 /* MockSignUpWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */; }; 835783C32B14A41600E7D304 /* MockLoginWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835783C22B14A41600E7D304 /* MockLoginWorker.swift */; }; 835783C62B14A5C800E7D304 /* LoginData.json in Resources */ = {isa = PBXBuildFile; fileRef = 835783C52B14A5C800E7D304 /* LoginData.json */; }; @@ -270,6 +272,8 @@ 8321A2FA2B1E1739000A12AF /* LOReportContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOReportContentView.swift; sourceTree = ""; }; 8321A2FC2B1E4260000A12AF /* ReportEndPointFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportEndPointFactory.swift; sourceTree = ""; }; 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportDTO.swift; sourceTree = ""; }; + 8321A3002B1F1EC5000A12AF /* MockReportWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockReportWorker.swift; sourceTree = ""; }; + 8321A3022B1F2041000A12AF /* ReportPlaybackVideo.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = ReportPlaybackVideo.json; sourceTree = ""; }; 834B7BD42B14F888002BD176 /* MockSignUpWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSignUpWorker.swift; sourceTree = ""; }; 835783C22B14A41600E7D304 /* MockLoginWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginWorker.swift; sourceTree = ""; }; 835783C52B14A5C800E7D304 /* LoginData.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LoginData.json; sourceTree = ""; }; @@ -515,6 +519,7 @@ 19A169462B17D12500DB34C0 /* MockTagPlayListWorker.swift */, 194C21BA2B1B718B00C62645 /* MockHomeWorker.swift */, 194C21BC2B1B728600C62645 /* StubAuthManager.swift */, + 8321A3002B1F1EC5000A12AF /* MockReportWorker.swift */, ); path = Mocks; sourceTree = ""; @@ -748,6 +753,7 @@ FC767F9A2B12283D0088CF9B /* PatchUserName.json */, 835783C52B14A5C800E7D304 /* LoginData.json */, 19A169352B178EA500DB34C0 /* PostList.json */, + 8321A3022B1F2041000A12AF /* ReportPlaybackVideo.json */, ); path = MockData; sourceTree = ""; @@ -1032,6 +1038,7 @@ 835A61A62B0B4DDD002F22A5 /* Dashboard-Regular.ttf in Resources */, 835783C62B14A5C800E7D304 /* LoginData.json in Resources */, FC767FA12B12283D0088CF9B /* DeleteUser.json in Resources */, + 8321A3032B1F2041000A12AF /* ReportPlaybackVideo.json in Resources */, FC7E45462AFEB62B004F155A /* LaunchScreen.storyboard in Resources */, 19A169362B178EA500DB34C0 /* PostList.json in Resources */, FC767FA02B12283D0088CF9B /* CheckUserName.json in Resources */, @@ -1132,6 +1139,7 @@ 193686722B15BCA7008902CD /* UserEndPointFactory.swift in Sources */, 194551F22B037F2D00299768 /* LoginPresenter.swift in Sources */, 194552242B0478B400299768 /* HomeModels.swift in Sources */, + 8321A3012B1F1EC5000A12AF /* MockReportWorker.swift in Sources */, 19A1692D2B17750B00DB34C0 /* Post.swift in Sources */, 194552022B038B8300299768 /* OSLog+.swift in Sources */, 8321A2F92B1E15F3000A12AF /* LOReportStackView.swift in Sources */, diff --git a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift index ffc61fd..e27f7be 100644 --- a/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOPopUpView.swift @@ -32,14 +32,20 @@ final class LOPopUpView: UIView { return button }() + weak var delegate: ReportViewControllerDelegate? + override init(frame: CGRect) { super.init(frame: frame) setConstraints() + cancelButton.addTarget(self, action: #selector(cancelButtonDidTap), for: .touchUpInside) + reportButton.addTarget(self, action: #selector(reportButtonDidTap), for: .touchUpInside) } required init?(coder: NSCoder) { super.init(coder: coder) setConstraints() + cancelButton.addTarget(self, action: #selector(cancelButtonDidTap), for: .touchUpInside) + reportButton.addTarget(self, action: #selector(reportButtonDidTap), for: .touchUpInside) } func getReportContent() -> String { @@ -73,4 +79,12 @@ final class LOPopUpView: UIView { ]) } + + @objc private func cancelButtonDidTap() { + delegate?.dismissReportView() + } + + @objc private func reportButtonDidTap() { + delegate?.reportPlaybackVideo(reportContent: getReportContent()) + } } diff --git a/iOS/Layover/Layover/Network/Mock/MockData/ReportPlaybackVideo.json b/iOS/Layover/Layover/Network/Mock/MockData/ReportPlaybackVideo.json new file mode 100644 index 0000000..52e9557 --- /dev/null +++ b/iOS/Layover/Layover/Network/Mock/MockData/ReportPlaybackVideo.json @@ -0,0 +1,10 @@ +{ + "customCode": "SUCCESS", + "message": "성공", + "statusCode": 200, + "data": { + "memberId": 221, + "boardId": 5, + "reportType": "청소년에게 유해한 내용이에요" + } +} diff --git a/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift b/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift index 93566fd..7de9c41 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportConfigurator.swift @@ -17,7 +17,8 @@ final class ReportConfigurator: Configurator { let viewController: ReportViewController = viewController let interactor: ReportInteractor = ReportInteractor() let presenter: ReportPresenter = ReportPresenter() - let worker: ReportWorker = ReportWorker() +// let worker: ReportWorker = ReportWorker() + let worker: ReportWorkerProtocol = MockReportWorker() let router: ReportRouter = ReportRouter() router.viewController = viewController diff --git a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift index 1979cdb..4cf1042 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift @@ -22,13 +22,15 @@ final class ReportInteractor: ReportBusinessLogic, ReportDataStore { typealias Models = ReportModels - lazy var worker = ReportWorker() + var worker: ReportWorkerProtocol? var presenter: ReportPresentationLogic? var boardID: Int? func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) { - guard let boardID else { return } + guard let boardID, + let worker + else { return } Task { let result: Bool = await worker.reportPlaybackVideo(boardId: boardID, reportContent: request.reportContent) let response: Models.ReportPlaybackVideo.Response = Models.ReportPlaybackVideo.Response(reportResult: result) diff --git a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift index 8a30191..53971d9 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift @@ -10,6 +10,7 @@ import UIKit protocol ReportViewControllerDelegate: AnyObject { func reportPlaybackVideo(reportContent: String) + func dismissReportView() } protocol ReportDisplayLogic: AnyObject { @@ -62,6 +63,7 @@ final class ReportViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() + popUpView.delegate = self } override func setUI() { @@ -96,11 +98,17 @@ extension ReportViewController: ReportViewControllerDelegate { let request: Models.ReportPlaybackVideo.Request = Models.ReportPlaybackVideo.Request(reportContent: reportContent) interactor?.reportPlaybackVideo(with: request) } + + func dismissReportView() { + self.dismiss(animated: false) + } } extension ReportViewController: ReportDisplayLogic { func displayReportResult(viewModel: ReportModels.ReportPlaybackVideo.ViewModel) { - Toast.shared.showToast(message: viewModel.reportMessage) + self.dismiss(animated: false, completion: { + Toast.shared.showToast(message: viewModel.reportMessage) + }) } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift index c206a9c..2e67054 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift @@ -31,14 +31,13 @@ final class ReportWorker: ReportWorkerProtocol { func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool { let endPoint = reportEndPointFactory.reportPlaybackVideoEndpoint(boardId: boardId, reportType: reportContent) do { - let responseData = try await provider.request(with: endPoint, authenticationIfNeeded: false) - print(responseData.message) + let responseData = try await provider.request(with: endPoint) guard let _ = responseData.data else { - os_log(.error, log: .default, "Failed to sign up with error: %@", responseData.message) + os_log(.error, log: .default, "Failed to report with error: %@", responseData.message) return false } } catch { - os_log(.error, log: .default, "Failed to sign up with error: %@", error.localizedDescription) + os_log(.error, log: .default, "Failed to report with error: %@", error.localizedDescription) return false } return true diff --git a/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift new file mode 100644 index 0000000..f63d81b --- /dev/null +++ b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift @@ -0,0 +1,50 @@ +// +// MockReportWorker.swift +// Layover +// +// Created by 황지웅 on 12/5/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation +import OSLog + +final class MockReportWorker: ReportWorkerProtocol { + + // MARK: - Properties + + private let provider: ProviderType = Provider(session: .initMockSession(), authManager: StubAuthManager()) + + // MARK: - Methods + + func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool { + guard let mockFileLocation = Bundle.main.url(forResource: "ReportPlaybackVideo", withExtension: "json"), + let mockData = try? Data(contentsOf: mockFileLocation) + else { + return false + } + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, + statusCode: 200, + httpVersion: nil, + headerFields: nil) + return (response, mockData, nil) + } + + do { + let bodyParameters = ReportDTO( + memberId: nil, + boardId: 1, + reportType: "청소년에게 유해한 내용입니다.") + let endPoint = EndPoint>(path: "/report", + method: .POST, + bodyParameters: bodyParameters) + let response = try await provider.request(with: endPoint) + return true + } catch { + os_log(.error, log: .data, "%@", error.localizedDescription) + return false + } + } +} From e4f5d5d431293cc87834fe61a55e8bc72e9a1e8d Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 19:19:42 +0900 Subject: [PATCH 17/25] =?UTF-8?q?=F0=9F=94=A7=20ProviderType=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=86=A0=EC=BD=9C=EC=97=90=20upload=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Network/Provider/Provider.swift | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index 0301e13..c11f94d 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -12,6 +12,12 @@ protocol ProviderType { func request(with endPoint: E, authenticationIfNeeded: Bool, retryCount: Int) async throws -> R where E.Response == R func request(url: URL) async throws -> Data func request(url: String) async throws -> Data + func upload(data: Data, to url: String, method: HTTPMethod) async throws -> Data + func backgroundUpload(fromFile: URL, + to url: String, + method: HTTPMethod, + sessionTaskDelegate: URLSessionTaskDelegate?, + delegateQueue: OperationQueue?) async throws -> Data } extension ProviderType { @@ -22,6 +28,25 @@ extension ProviderType { authenticationIfNeeded: authenticationIfNeeded, retryCount: retryCount) } + + func upload(data: Data, to url: String, method: HTTPMethod = .PUT) async throws -> Data { + return try await upload(data: data, + to: url, + method: method) + } + + func backgroundUpload(fromFile: URL, + to url: String, + method: HTTPMethod = .PUT, + sessionTaskDelegate: URLSessionTaskDelegate? = nil, + delegateQueue: OperationQueue? = nil) async throws -> Data { + return try await backgroundUpload(fromFile: fromFile, + to: url, + method: method, + sessionTaskDelegate: sessionTaskDelegate, + delegateQueue: delegateQueue) + } + } class Provider: ProviderType { From 544660a9b38681ce4911d4c46de386df014c6837 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 5 Dec 2023 19:20:00 +0900 Subject: [PATCH 18/25] =?UTF-8?q?=E2=9C=A8=20UploadPostWorker=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 16 +++++ iOS/Layover/Layover/Models/UploadPost.swift | 17 ++++++ .../Layover/Network/DTOs/UploadPostDTO.swift | 24 ++++++++ .../Layover/Network/DTOs/UploadVideoDTO.swift | 18 ++++++ .../Factories/UploadPostEndPointFactory.swift | 42 +++++++++++++ .../UploadPost/UploadPostInteractor.swift | 45 ++++++++------ .../Scenes/UploadPost/UploadPostWorker.swift | 60 ++++++++++++++++++- 7 files changed, 203 insertions(+), 19 deletions(-) create mode 100644 iOS/Layover/Layover/Models/UploadPost.swift create mode 100644 iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift create mode 100644 iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift create mode 100644 iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 65b6a36..4928afd 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -132,6 +132,10 @@ FC3752352B170A620000D439 /* EditVideoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC37522F2B170A620000D439 /* EditVideoInteractor.swift */; }; FC3752372B170B230000D439 /* EditVideoConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC3752362B170B230000D439 /* EditVideoConfigurator.swift */; }; FC3F3BD82B069EB30080E3A6 /* MapCarouselCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC3F3BD72B069EB30080E3A6 /* MapCarouselCollectionViewCell.swift */; }; + FC4084C42B1F14F600CE4727 /* UploadPostEndPointFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4084C32B1F14F600CE4727 /* UploadPostEndPointFactory.swift */; }; + FC4084C62B1F1C5B00CE4727 /* UploadPostDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4084C52B1F1C5B00CE4727 /* UploadPostDTO.swift */; }; + FC4084CA2B1F291200CE4727 /* UploadVideoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4084C92B1F291200CE4727 /* UploadVideoDTO.swift */; }; + FC4084CC2B1F2F5D00CE4727 /* UploadPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4084CB2B1F2F5D00CE4727 /* UploadPost.swift */; }; FC42E4142B17AB69005D4956 /* VideoFileWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC42E4132B17AB69005D4956 /* VideoFileWorker.swift */; }; FC49758F2B03432800D8627F /* Pretendard-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC4975862B03432700D8627F /* Pretendard-SemiBold.ttf */; }; FC4975932B03432800D8627F /* Pretendard-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */; }; @@ -321,6 +325,10 @@ FC37522F2B170A620000D439 /* EditVideoInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditVideoInteractor.swift; sourceTree = ""; }; FC3752362B170B230000D439 /* EditVideoConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditVideoConfigurator.swift; sourceTree = ""; }; FC3F3BD72B069EB30080E3A6 /* MapCarouselCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCarouselCollectionViewCell.swift; sourceTree = ""; }; + FC4084C32B1F14F600CE4727 /* UploadPostEndPointFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostEndPointFactory.swift; sourceTree = ""; }; + FC4084C52B1F1C5B00CE4727 /* UploadPostDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostDTO.swift; sourceTree = ""; }; + FC4084C92B1F291200CE4727 /* UploadVideoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadVideoDTO.swift; sourceTree = ""; }; + FC4084CB2B1F2F5D00CE4727 /* UploadPost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPost.swift; sourceTree = ""; }; FC42E4132B17AB69005D4956 /* VideoFileWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoFileWorker.swift; sourceTree = ""; }; FC4975862B03432700D8627F /* Pretendard-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.ttf"; sourceTree = ""; }; FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Bold.ttf"; sourceTree = ""; }; @@ -492,6 +500,7 @@ 1972CCD72B13A2EC00C3C762 /* SignUpEndPointFactory.swift */, 193686712B15BCA7008902CD /* UserEndPointFactory.swift */, 19A1693F2B17C10300DB34C0 /* PostEndPointFactory.swift */, + FC4084C32B1F14F600CE4727 /* UploadPostEndPointFactory.swift */, ); path = Factories; sourceTree = ""; @@ -525,6 +534,7 @@ 19A1692C2B17750B00DB34C0 /* Post.swift */, 19A169412B17C70C00DB34C0 /* Member.swift */, 19A169432B17C71C00DB34C0 /* Board.swift */, + FC4084CB2B1F2F5D00CE4727 /* UploadPost.swift */, ); path = Models; sourceTree = ""; @@ -565,6 +575,8 @@ 19A1693B2B17BD1C00DB34C0 /* BoardDTO.swift */, 836C338C2B15D91F00ECAFB0 /* VideoDTO.swift */, 836C338E2B160CC700ECAFB0 /* MemberDTO.swift */, + FC4084C52B1F1C5B00CE4727 /* UploadPostDTO.swift */, + FC4084C92B1F291200CE4727 /* UploadVideoDTO.swift */, ); path = DTOs; sourceTree = ""; @@ -1116,6 +1128,7 @@ FC3752342B170A620000D439 /* EditVideoViewController.swift in Sources */, 19A169442B17C71C00DB34C0 /* Board.swift in Sources */, 194552252B0478B400299768 /* HomeViewController.swift in Sources */, + FC4084C62B1F1C5B00CE4727 /* UploadPostDTO.swift in Sources */, 194552222B0478B400299768 /* HomeWorker.swift in Sources */, 194C21BD2B1B728600C62645 /* StubAuthManager.swift in Sources */, FC5BE1202B148D170036366D /* EditProfileViewController.swift in Sources */, @@ -1193,6 +1206,7 @@ FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */, FC767F862B1214C10088CF9B /* CheckUserNameDTO.swift in Sources */, 836C338D2B15D91F00ECAFB0 /* VideoDTO.swift in Sources */, + FC4084C42B1F14F600CE4727 /* UploadPostEndPointFactory.swift in Sources */, FC5BE1232B1490660036366D /* EditProfileConfigurator.swift in Sources */, 1945522A2B04883800299768 /* UIView+.swift in Sources */, FCE52FFA2B0FCB0A002CDB75 /* URLSession+.swift in Sources */, @@ -1214,6 +1228,7 @@ FC930E752B0CD75C00AA48E3 /* ProfilePresenter.swift in Sources */, 835A61A12B068115002F22A5 /* PlaybackViewController.swift in Sources */, 1945520F2B03AEA400299768 /* Configurator.swift in Sources */, + FC4084CC2B1F2F5D00CE4727 /* UploadPost.swift in Sources */, 835A619F2B068115002F22A5 /* PlaybackRouter.swift in Sources */, 19A1693C2B17BD1C00DB34C0 /* BoardDTO.swift in Sources */, FC2511B12B04EAEC004717BC /* MapModels.swift in Sources */, @@ -1238,6 +1253,7 @@ 835A61A22B068115002F22A5 /* PlaybackInteractor.swift in Sources */, 19AACFCC2B0F7D730088143E /* LoginDTO.swift in Sources */, FC0E803C2B1B91C900EF56D6 /* EditTagRouter.swift in Sources */, + FC4084CA2B1F291200CE4727 /* UploadVideoDTO.swift in Sources */, 835A61942B068096002F22A5 /* LODescriptionView.swift in Sources */, FC68E2A52B0233D3001AABFF /* Provider.swift in Sources */, FC930E7E2B0CDB2900AA48E3 /* ThumbnailCollectionViewCell.swift in Sources */, diff --git a/iOS/Layover/Layover/Models/UploadPost.swift b/iOS/Layover/Layover/Models/UploadPost.swift new file mode 100644 index 0000000..3bc48c6 --- /dev/null +++ b/iOS/Layover/Layover/Models/UploadPost.swift @@ -0,0 +1,17 @@ +// +// UploadPost.swift +// Layover +// +// Created by kong on 2023/12/05. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +struct UploadPost { + let title: String + let content: String? + let latitude, longitude: Double + let tag: [String] + let videoURL: URL +} diff --git a/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift b/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift new file mode 100644 index 0000000..74158e4 --- /dev/null +++ b/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift @@ -0,0 +1,24 @@ +// +// UploadPostDTO.swift +// Layover +// +// Created by kong on 2023/12/05. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +struct UploadPostDTO: Decodable { + let id: Int + let title + let content: String? + let latitude, longitude: Double + let tag: [String] +} + +struct UploadPostRequestDTO: Encodable { + let title: String + let content: String? + let latitude, longitude: Double + let tag: [String] +} diff --git a/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift new file mode 100644 index 0000000..9426621 --- /dev/null +++ b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift @@ -0,0 +1,18 @@ +// +// UploadVideoDTO.swift +// Layover +// +// Created by kong on 2023/12/05. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +struct UploadVideoDTO: Decodable { + let presignedUrl: String +} + +struct UploadVideoRequestDTO: Encodable { + let boardID: Int + let filetype: String +} diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift new file mode 100644 index 0000000..8345709 --- /dev/null +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift @@ -0,0 +1,42 @@ +// +// UploadPostEndPointFactory.swift +// Layover +// +// Created by kong on 2023/12/05. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +protocol UploadPostEndPointFactory { + func makeUploadPostEndPoint(title: String, + content: String?, + latitude: Double, + longitude: Double, + tag: [String]) -> EndPoint> + func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> +} + +final class DefaultUploadPostEndPointFactory: UploadPostEndPointFactory { + + func makeUploadPostEndPoint(title: String, + content: String?, + latitude: Double, + longitude: Double, + tag: [String]) -> EndPoint> { + return EndPoint(path: "/board", + method: .POST, + bodyParameters: UploadPostRequestDTO(title: title, + content: content, + latitude: latitude, + longitude: longitude, + tag: tag)) + } + + func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> { + return EndPoint(path: "/board/presigned-url", + method: .POST, + bodyParameters: UploadVideoRequestDTO(boardID: boardID, + filetype: fileType)) + } +} diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 4c93877..b776105 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -17,7 +17,7 @@ protocol UploadPostBusinessLogic { func fetchThumbnailImage() func fetchCurrentAddress() func canUploadPost(request: UploadPostModels.CanUploadPost.Request) - func uploadPost() + func uploadPost(request: UploadPostModels.UploadPost.Request) } protocol UploadPostDataStore { @@ -35,6 +35,7 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { lazy var worker = UploadPostWorker() var presenter: UploadPostPresentationLogic? + private let fileManager: FileManager private let locationManager: CLLocationManager // MARK: - Data Store @@ -43,10 +44,16 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { var isMuted: Bool? var tags: [String]? = [] - init(locationManager: CLLocationManager = CLLocationManager()) { + // MARK: - Object LifeCycle + + init(fileManager: FileManager = FileManager.default, + locationManager: CLLocationManager = CLLocationManager()) { + self.fileManager = fileManager self.locationManager = locationManager } + // MARK: - Methods + func fetchTags() { guard let tags else { return } presenter?.presentTags(with: UploadPostModels.FetchTags.Response(tags: tags)) @@ -64,7 +71,7 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { presenter?.presentThumnailImage(with: UploadPostModels.FetchThumbnail.Response(thumnailImage: image)) } } catch let error { - os_log(.error, log: .default, "Failed to fetch ThumbnailImage with error: %@", error.localizedDescription) + os_log(.error, log: .default, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) } } } @@ -74,9 +81,8 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { locationManager.startUpdatingLocation() guard let space = locationManager.location?.coordinate else { return } - let latitude = space.latitude - let longitude = space.longitude - let location = CLLocation(latitude: latitude, longitude: longitude) + let location = CLLocation(latitude: space.latitude, + longitude: space.longitude) let locale = Locale(identifier: "ko_KR") Task { @@ -91,6 +97,8 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { await MainActor.run { presenter?.presentCurrentAddress(with: response) } + } catch { + os_log(.error, log: .default, "Failed to fetch Current Address with error: %@", error.localizedDescription) } } } @@ -100,22 +108,23 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { presenter?.presentUploadButton(with: response) } - func uploadPost() { - guard let isMuted else { return } + func uploadPost(request: UploadPostModels.UploadPost.Request) { + guard let videoURL, let isMuted else { return } if isMuted { - extractVideoWithoutAudio() + exportVideoWithoutAudio(at: videoURL) } - } - private func extractVideoWithoutAudio() { - guard let videoURL else { return } + + } + + // isMuted가 true인 경우, 새로운 Composition을 만들어서 영상 재추출해서 업로드 + private func exportVideoWithoutAudio(at url: URL) { let composition = AVMutableComposition() - let sourceAsset = AVURLAsset(url: videoURL) + let sourceAsset = AVURLAsset(url: url) guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return } - Task { do { let sourceAssetduration = try await sourceAsset.load(.duration) @@ -127,16 +136,16 @@ final class UploadPostInteractor: UploadPostBusinessLogic, UploadPostDataStore { of: sourceVideoTrack, at: .zero) - if FileManager.default.fileExists(atPath: videoURL.path()) { - try FileManager.default.removeItem(at: videoURL) + if fileManager.fileExists(atPath: url.path()) { + try fileManager.removeItem(at: url) } let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) exporter?.outputURL = videoURL exporter?.outputFileType = AVFileType.mov await exporter?.export() - } catch let error { - os_log(.error, log: .default, "Failed to fetch extractVideoWithoutAudio with error: %@", error.localizedDescription) + } catch { + os_log(.error, log: .default, "Failed to extract Video Without Audio with error: %@", error.localizedDescription) } } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 08db923..24b3852 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -8,12 +8,70 @@ import UIKit -class UploadPostWorker { +import OSLog + +protocol UploadPostWorkerProtocol { + func uploadPost(with request: UploadPost, + url: URL) async -> UploadPostDTO? +} + +final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { // MARK: - Properties typealias Models = UploadPostModels + private let provider: ProviderType + private let uploadPostEndPointFactory: UploadPostEndPointFactory // MARK: - Methods + init(provider: ProviderType = Provider(), + uploadPostEndPointFactory: UploadPostEndPointFactory = DefaultUploadPostEndPointFactory()) { + self.provider = provider + self.uploadPostEndPointFactory = uploadPostEndPointFactory + } + + func uploadPost(with request: UploadPost, + url: URL) async -> UploadPostDTO? { + let endPoint = uploadPostEndPointFactory.makeUploadPostEndPoint(title: request.title, + content: request.content, + latitude: request.latitude, + longitude: request.longitude, + tag: request.tag) + do { + let response = try await provider.request(with: endPoint) + + guard let boardID = response.data?.id else { return nil } + + let fileType = request.videoURL.pathExtension + let upload = await uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, + filetype: fileType), + videoURL: url) + return response.data + } catch { + os_log(.error, log: .default, "Failed to fetch posts: %@", error.localizedDescription) + return nil + } + } + + private func uploadVideo(with request: UploadVideoRequestDTO, + videoURL: URL) async -> Bool { + let endPoint = uploadPostEndPointFactory.makeUploadVideoEndPoint(boardID: request.boardID, + fileType: request.filetype) + do { + let response = try await provider.request(with: endPoint) + guard let presignedURLString = response.data?.presignedUrl else { return false } + let uploadResponse = try await provider.backgroundUpload(fromFile: videoURL, + to: presignedURLString, + sessionTaskDelegate: self) + return true + } catch { + return false + } + } + +} + +extension UploadPostWorker: URLSessionTaskDelegate { + } From 0389e277272c28eef7e271725ea6bf39074916a0 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:47:17 +0900 Subject: [PATCH 19/25] =?UTF-8?q?:recycle:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/Playback/PlaybackViewController.swift | 6 ++---- .../Layover/Scenes/Report/ReportInteractor.swift | 12 +++++++----- .../Layover/Scenes/Report/ReportModels.swift | 15 ++++++++++++++- .../Layover/Scenes/Report/ReportPresenter.swift | 4 ++-- .../Scenes/Report/ReportViewController.swift | 10 +++++----- .../Layover/Scenes/Report/ReportWorker.swift | 4 ++-- .../Layover/Workers/Mocks/MockReportWorker.swift | 1 + 7 files changed, 33 insertions(+), 19 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift index 3065965..eade8f0 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackViewController.swift @@ -46,8 +46,6 @@ final class PlaybackViewController: BaseViewController { button.setImage(UIImage(systemName: "ellipsis"), for: .normal) let barButtonItem: UIBarButtonItem = UIBarButtonItem(customView: button) barButtonItem.customView?.transform = CGAffineTransform(rotationAngle: .pi / 2) - barButtonItem.target = PlaybackViewController.self - barButtonItem.action = #selector(reportButtonDidTap) return barButtonItem }() @@ -177,8 +175,8 @@ final class PlaybackViewController: BaseViewController { @objc private func reportButtonDidTap() { let alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let reportAction: UIAlertAction = UIAlertAction(title: "신고", style: .destructive, handler: { - _ in - self.router?.routeToReport() + [weak self] _ in + self?.router?.routeToReport() }) let cancelAction: UIAlertAction = UIAlertAction(title: "취소", style: .cancel) alert.addAction(reportAction) diff --git a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift index 4cf1042..cf71b29 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift @@ -9,7 +9,8 @@ import UIKit protocol ReportBusinessLogic { - func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) + @discardableResult + func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) -> Task } protocol ReportDataStore { @@ -27,16 +28,17 @@ final class ReportInteractor: ReportBusinessLogic, ReportDataStore { var boardID: Int? - func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) { - guard let boardID, - let worker - else { return } + func reportPlaybackVideo(with request: ReportModels.ReportPlaybackVideo.Request) -> Task { Task { + guard let boardID, + let worker + else { return false } let result: Bool = await worker.reportPlaybackVideo(boardId: boardID, reportContent: request.reportContent) let response: Models.ReportPlaybackVideo.Response = Models.ReportPlaybackVideo.Response(reportResult: result) await MainActor.run { presenter?.presentReportPlaybackVideo(with: response) } + return true } } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportModels.swift b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift index 8a3c571..f7cd3d6 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportModels.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportModels.swift @@ -13,6 +13,19 @@ enum ReportModels { // MARK: - Use Cases enum ReportPlaybackVideo { + enum ReportMessage { + case success + case fail + + var description: String { + switch self { + case .success: + "신고가 접수되었습니다." + case .fail: + "신고 접수에 실패했습니다. 다시 시도해주세요." + } + } + } struct Request { let reportContent: String } @@ -22,7 +35,7 @@ enum ReportModels { } struct ViewModel { - let reportMessage: String + let reportMessage: ReportMessage } } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift index b5e5b40..906fba0 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift @@ -22,9 +22,9 @@ final class ReportPresenter: ReportPresentationLogic { func presentReportPlaybackVideo(with response: ReportModels.ReportPlaybackVideo.Response) { let viewModel: Models.ReportPlaybackVideo.ViewModel if response.reportResult { - viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: "신고가 접수되었습니다.") + viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: .success) } else { - viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: "신고 접수에 실패했습니다. 다시 시도해주세요.") + viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: .fail) } viewController?.displayReportResult(viewModel: viewModel) } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift index 53971d9..07c8cac 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportViewController.swift @@ -63,11 +63,11 @@ final class ReportViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - popUpView.delegate = self } override func setUI() { super.setUI() + popUpView.delegate = self } override func setConstraints() { @@ -89,7 +89,7 @@ final class ReportViewController: BaseViewController { } @objc private func cancelButtonDidTap() { - self.dismiss(animated: true) + dismiss(animated: true) } } @@ -100,14 +100,14 @@ extension ReportViewController: ReportViewControllerDelegate { } func dismissReportView() { - self.dismiss(animated: false) + dismiss(animated: false) } } extension ReportViewController: ReportDisplayLogic { func displayReportResult(viewModel: ReportModels.ReportPlaybackVideo.ViewModel) { - self.dismiss(animated: false, completion: { - Toast.shared.showToast(message: viewModel.reportMessage) + dismiss(animated: false, completion: { + Toast.shared.showToast(message: viewModel.reportMessage.description) }) } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift index 2e67054..6abd901 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift @@ -33,11 +33,11 @@ final class ReportWorker: ReportWorkerProtocol { do { let responseData = try await provider.request(with: endPoint) guard let _ = responseData.data else { - os_log(.error, log: .default, "Failed to report with error: %@", responseData.message) + os_log(.error, log: .data, "Failed to report with error: %@", responseData.message) return false } } catch { - os_log(.error, log: .default, "Failed to report with error: %@", error.localizedDescription) + os_log(.error, log: .data, "Failed to report with error: %@", error.localizedDescription) return false } return true diff --git a/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift index f63d81b..27cda19 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift @@ -21,6 +21,7 @@ final class MockReportWorker: ReportWorkerProtocol { guard let mockFileLocation = Bundle.main.url(forResource: "ReportPlaybackVideo", withExtension: "json"), let mockData = try? Data(contentsOf: mockFileLocation) else { + os_log(.error, log: .data, "Failed to generate mock with error: %@", "Generate File Error") return false } From f6b36e36eaa2eb31825f2628be121fc9406d3f34 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 6 Dec 2023 07:02:50 +0900 Subject: [PATCH 20/25] =?UTF-8?q?=E2=9C=A8=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=97=B0=EA=B2=B0,?= =?UTF-8?q?=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=A0=88=EC=8A=A4=20=EB=B0=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/DesignSystem/LOTagStackView.swift | 6 ++ .../Extensions/Notification.Name+.swift | 2 + .../Layover/Network/DTOs/UploadPostDTO.swift | 2 +- .../Layover/Network/DTOs/UploadVideoDTO.swift | 11 ++- .../Layover/Network/Provider/Provider.swift | 3 +- iOS/Layover/Layover/SceneDelegate.swift | 45 ++++++++++- .../Scenes/EditTag/EditTagRouter.swift | 12 +-- .../EditTag/EditTagViewController.swift | 6 +- .../EditVideo/EditVideoViewController.swift | 2 +- .../UploadPost/UploadPostConfigurator.swift | 3 + .../UploadPost/UploadPostInteractor.swift | 80 ++++++++++++++----- .../Scenes/UploadPost/UploadPostModels.swift | 2 - .../UploadPost/UploadPostPresenter.swift | 25 ++++-- .../Scenes/UploadPost/UploadPostRouter.swift | 7 +- .../UploadPost/UploadPostViewController.swift | 11 ++- .../Scenes/UploadPost/UploadPostWorker.swift | 39 ++++----- 16 files changed, 184 insertions(+), 72 deletions(-) diff --git a/iOS/Layover/Layover/DesignSystem/LOTagStackView.swift b/iOS/Layover/Layover/DesignSystem/LOTagStackView.swift index 95f5b47..df6e2b8 100644 --- a/iOS/Layover/Layover/DesignSystem/LOTagStackView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOTagStackView.swift @@ -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 diff --git a/iOS/Layover/Layover/Extensions/Notification.Name+.swift b/iOS/Layover/Layover/Extensions/Notification.Name+.swift index d48a1b0..bec37cf 100644 --- a/iOS/Layover/Layover/Extensions/Notification.Name+.swift +++ b/iOS/Layover/Layover/Extensions/Notification.Name+.swift @@ -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") } diff --git a/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift b/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift index 74158e4..62016db 100644 --- a/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/UploadPostDTO.swift @@ -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] diff --git a/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift index 9426621..0d8160a 100644 --- a/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift @@ -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 + } } diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index c11f94d..eec9b1c 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -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) diff --git a/iOS/Layover/Layover/SceneDelegate.swift b/iOS/Layover/Layover/SceneDelegate.swift index 41a0756..fb149ad 100644 --- a/iOS/Layover/Layover/SceneDelegate.swift +++ b/iOS/Layover/Layover/SceneDelegate.swift @@ -11,6 +11,7 @@ import KakaoSDKUser class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var progressView: UIProgressView = UIProgressView(progressViewStyle: .bar) var window: UIWindow? @@ -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() @@ -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() { @@ -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: "업로드가 완료되었습니다 ✨") + } + } } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift index bb65454..d499382 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagRouter.swift @@ -16,7 +16,7 @@ protocol EditTagDataPassing { var dataStore: EditTagDataStore? { get } } -class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing { +final class EditTagRouter: NSObject, EditTagRoutingLogic, EditTagDataPassing { // MARK: - Properties @@ -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) } diff --git a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift index 9d93cf3..dd82629 100644 --- a/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditTag/EditTagViewController.swift @@ -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() } @@ -110,7 +109,6 @@ extension EditTagViewController: UITextFieldDelegate { } } - extension EditTagViewController: EditTagDisplayLogic { func displayTags(viewModel: EditTagModels.FetchTags.ViewModel) { diff --git a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift index 0f9754c..e62532c 100644 --- a/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditVideo/EditVideoViewController.swift @@ -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) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift index e9215b2..85794a6 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift @@ -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 } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index b776105..37aebaf 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -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 } protocol UploadPostDataStore { @@ -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 @@ -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() { @@ -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) @@ -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 { @@ -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 { + 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) @@ -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: 완료 토스트 + + } +} diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift index 246988b..e722fff 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostModels.swift @@ -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 { diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift index 20c86fa..f87cef8 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostPresenter.swift @@ -13,6 +13,7 @@ protocol UploadPostPresentationLogic { func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) +// func presentUploadProgress(with response: UploadPostModels.UploadPost.Response) } final class UploadPostPresenter: UploadPostPresentationLogic { @@ -23,25 +24,35 @@ final class UploadPostPresenter: UploadPostPresentationLogic { weak var viewController: UploadPostDisplayLogic? func presentTags(with response: UploadPostModels.FetchTags.Response) { - viewController?.displayTags(viewModel: UploadPostModels.FetchTags.ViewModel(tags: response.tags)) + viewController?.displayTags(viewModel: Models.FetchTags.ViewModel(tags: response.tags)) } func presentThumnailImage(with response: UploadPostModels.FetchThumbnail.Response) { let image = UIImage(cgImage: response.thumnailImage) - viewController?.displayThumbnail(viewModel: UploadPostModels.FetchThumbnail.ViewModel(thumnailImage: image)) + viewController?.displayThumbnail(viewModel: Models.FetchThumbnail.ViewModel(thumnailImage: image)) } func presentCurrentAddress(with response: UploadPostModels.FetchCurrentAddress.Response) { - let addressSet = Set([response.administrativeArea, response.locality, response.subLocality]) - let fullAddress: String = Array(addressSet) + let addresses: [String] = [ + response.administrativeArea, + response.locality, + response.subLocality] .compactMap { $0 } - .joined(separator: " ") - let viewModel = UploadPostModels.FetchCurrentAddress.ViewModel(fullAddress: fullAddress) + + var fullAddress: [String] = [] + + for address in addresses { + if !fullAddress.contains(address) { + fullAddress.append(address) + } + } + + let viewModel = Models.FetchCurrentAddress.ViewModel(fullAddress: fullAddress.joined(separator: " ")) viewController?.displayCurrentAddress(viewModel: viewModel) } func presentUploadButton(with response: UploadPostModels.CanUploadPost.Response) { - let viewModel = UploadPostModels.CanUploadPost.ViewModel(canUpload: !response.isEmpty) + let viewModel = Models.CanUploadPost.ViewModel(canUpload: !response.isEmpty) viewController?.displayUploadButton(viewModel: viewModel) } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift index 2d838ba..4b9b74f 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostRouter.swift @@ -10,13 +10,14 @@ import UIKit protocol UploadPostRoutingLogic { func routeToNext() + func routeToBack() } protocol UploadPostDataPassing { var dataStore: UploadPostDataStore? { get } } -class UploadPostRouter: NSObject, UploadPostRoutingLogic, UploadPostDataPassing { +final class UploadPostRouter: NSObject, UploadPostRoutingLogic, UploadPostDataPassing { // MARK: - Properties @@ -36,4 +37,8 @@ class UploadPostRouter: NSObject, UploadPostRoutingLogic, UploadPostDataPassing viewController?.present(nextViewController, animated: true) } + func routeToBack() { + viewController?.navigationController?.popToRootViewController(animated: true) + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 4f8a972..f16890e 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -42,7 +42,7 @@ final class UploadPostViewController: BaseViewController { return imageLabel }() - private let titleTextField: LOTextField = { + private lazy var titleTextField: LOTextField = { let textField = LOTextField() textField.placeholder = "제목" textField.addTarget(self, action: #selector(titleTextChanged), for: .editingChanged) @@ -228,7 +228,7 @@ final class UploadPostViewController: BaseViewController { } @objc private func titleTextChanged() { - interactor?.canUploadPost(request: UploadPostModels.CanUploadPost.Request(title: titleTextField.text)) + interactor?.canUploadPost(request: Models.CanUploadPost.Request(title: titleTextField.text)) } @objc private func viewDidTap() { @@ -240,7 +240,12 @@ final class UploadPostViewController: BaseViewController { } @objc private func uploadButtonDidTap() { - interactor?.uploadPost() + guard let title = titleTextField.text else { return } + let request = Models.UploadPost.Request(title: title, + content: contentTextView.text, + tags: tagStackView.tags) + interactor?.uploadPost(request: request) + router?.routeToBack() } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 24b3852..5ff3354 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -11,8 +11,7 @@ import UIKit import OSLog protocol UploadPostWorkerProtocol { - func uploadPost(with request: UploadPost, - url: URL) async -> UploadPostDTO? + func uploadPost(with request: UploadPost) async -> Bool } final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { @@ -23,6 +22,8 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { private let provider: ProviderType private let uploadPostEndPointFactory: UploadPostEndPointFactory + weak var sessionTaskDelegate: URLSessionTaskDelegate? + // MARK: - Methods init(provider: ProviderType = Provider(), @@ -31,8 +32,7 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { self.uploadPostEndPointFactory = uploadPostEndPointFactory } - func uploadPost(with request: UploadPost, - url: URL) async -> UploadPostDTO? { + func uploadPost(with request: UploadPost) async -> Bool { let endPoint = uploadPostEndPointFactory.makeUploadPostEndPoint(title: request.title, content: request.content, latitude: request.latitude, @@ -40,38 +40,31 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { tag: request.tag) do { let response = try await provider.request(with: endPoint) - - guard let boardID = response.data?.id else { return nil } - + guard let boardID = response.data?.id else { return false } let fileType = request.videoURL.pathExtension - let upload = await uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, - filetype: fileType), - videoURL: url) - return response.data + let uploadResponse = await uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, filetype: fileType), + videoURL: request.videoURL) + return uploadResponse } catch { - os_log(.error, log: .default, "Failed to fetch posts: %@", error.localizedDescription) - return nil + os_log(.error, log: .data, "Failed to fetch posts: %@", error.localizedDescription) + return false } } - private func uploadVideo(with request: UploadVideoRequestDTO, - videoURL: URL) async -> Bool { + private func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool { let endPoint = uploadPostEndPointFactory.makeUploadVideoEndPoint(boardID: request.boardID, fileType: request.filetype) do { let response = try await provider.request(with: endPoint) - guard let presignedURLString = response.data?.presignedUrl else { return false } - let uploadResponse = try await provider.backgroundUpload(fromFile: videoURL, - to: presignedURLString, - sessionTaskDelegate: self) + guard let preSignedURLString = response.data?.preSignedURL else { return false } + let data = try await provider.backgroundUpload(fromFile: videoURL, + to: preSignedURLString, + sessionTaskDelegate: sessionTaskDelegate) return true } catch { + os_log(.error, log: .data, "Failed to upload Video: %@", error.localizedDescription) return false } } } - -extension UploadPostWorker: URLSessionTaskDelegate { - -} From d7bdb5e93e92155d155ada9766a6865ce9c0ce99 Mon Sep 17 00:00:00 2001 From: loinsir Date: Wed, 6 Dec 2023 14:12:30 +0900 Subject: [PATCH 21/25] =?UTF-8?q?:wrench:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EB=B3=80=EA=B2=BD,=20Mock=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Network/DTOs/CheckSignUpDTO.swift | 2 +- .../Layover/Network/Mock/MockData/CheckSignUp.json | 2 +- .../Layover/Network/Mock/MockData/LoginData.json | 14 +++++++------- .../Layover/Scenes/Login/LoginInteractor.swift | 8 ++++---- iOS/Layover/Layover/Scenes/Login/LoginWorker.swift | 4 ++-- .../Layover/Workers/Mocks/MockLoginWorker.swift | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/iOS/Layover/Layover/Network/DTOs/CheckSignUpDTO.swift b/iOS/Layover/Layover/Network/DTOs/CheckSignUpDTO.swift index 3a1ea7c..f50d020 100644 --- a/iOS/Layover/Layover/Network/DTOs/CheckSignUpDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/CheckSignUpDTO.swift @@ -9,5 +9,5 @@ import Foundation struct CheckSignUpDTO: Decodable { - let isValid: Bool + let isAlreadyExist: Bool } diff --git a/iOS/Layover/Layover/Network/Mock/MockData/CheckSignUp.json b/iOS/Layover/Layover/Network/Mock/MockData/CheckSignUp.json index a976fb8..254727a 100644 --- a/iOS/Layover/Layover/Network/Mock/MockData/CheckSignUp.json +++ b/iOS/Layover/Layover/Network/Mock/MockData/CheckSignUp.json @@ -3,6 +3,6 @@ "message": "성공", "statusCode": 200, "data": { - "isValid": true + "isAlreadyExist": true } } diff --git a/iOS/Layover/Layover/Network/Mock/MockData/LoginData.json b/iOS/Layover/Layover/Network/Mock/MockData/LoginData.json index 2fd9362..f378e18 100644 --- a/iOS/Layover/Layover/Network/Mock/MockData/LoginData.json +++ b/iOS/Layover/Layover/Network/Mock/MockData/LoginData.json @@ -1,9 +1,9 @@ { - "customCode": "SUCCESS", - "message": "성공", - "statusCode": 200, - "data": { - "accessToken": "mockAccessToken", - "refreshToken": "mockRefreshToken" - } + "customCode": "SUCCESS", + "message": "성공", + "statusCode": 200, + "data": { + "accessToken": "mockAccessToken", + "refreshToken": "mockRefreshToken" + } } diff --git a/iOS/Layover/Layover/Scenes/Login/LoginInteractor.swift b/iOS/Layover/Layover/Scenes/Login/LoginInteractor.swift index 1292fbf..0b63af2 100644 --- a/iOS/Layover/Layover/Scenes/Login/LoginInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Login/LoginInteractor.swift @@ -43,7 +43,7 @@ extension LoginInteractor: LoginBusinessLogic { guard let token = await worker?.fetchKakaoLoginToken() else { return } kakaoLoginToken = token if await worker?.isRegisteredKakao(with: token) == true, - await worker?.loginKakao(with: token) == true { + await worker?.loginKakao(with: token) == true { await MainActor.run { presenter?.presentPerformLogin() } @@ -77,10 +77,10 @@ extension LoginInteractor: ASAuthorizationControllerDelegate { } appleLoginToken = identityToken Task { - async let isRegistered: Bool = worker?.isRegisteredApple(with: identityToken) ?? false - async let loginResult: Bool = worker?.loginApple(with: identityToken) ?? false + let isRegistered = await worker?.isRegisteredApple(with: identityToken) + let loginResult = await worker?.loginApple(with: identityToken) - if await !isRegistered, await loginResult { + if isRegistered == true, loginResult == true { await MainActor.run { presenter?.presentPerformLogin() } diff --git a/iOS/Layover/Layover/Scenes/Login/LoginWorker.swift b/iOS/Layover/Layover/Scenes/Login/LoginWorker.swift index ff6acd3..e315645 100644 --- a/iOS/Layover/Layover/Scenes/Login/LoginWorker.swift +++ b/iOS/Layover/Layover/Scenes/Login/LoginWorker.swift @@ -73,7 +73,7 @@ extension LoginWorker: LoginWorkerProtocol { do { let endPoint = loginEndPointFactory.makeCheckKakaoIsSignedUpEndPoint(with: socialToken) let result = try await provider.request(with: endPoint, authenticationIfNeeded: false) - return result.data?.isValid + return result.data?.isAlreadyExist } catch { os_log(.error, log: .data, "%@", error.localizedDescription) return nil @@ -101,7 +101,7 @@ extension LoginWorker: LoginWorkerProtocol { do { let endPoint = loginEndPointFactory.makeCheckAppleIsSignedUpEndPoint(with: identityToken) let result = try await provider.request(with: endPoint, authenticationIfNeeded: false) - return result.data?.isValid + return result.data?.isAlreadyExist } catch { os_log(.error, log: .data, "%@", error.localizedDescription) return nil diff --git a/iOS/Layover/Layover/Workers/Mocks/MockLoginWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockLoginWorker.swift index 9aab6ce..b94518c 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockLoginWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockLoginWorker.swift @@ -49,7 +49,7 @@ final class MockLoginWorker: LoginWorkerProtocol { method: .POST, bodyParameters: bodyParameters) let response = try await provider.request(with: endPoint, authenticationIfNeeded: false, retryCount: 0) - return response.data?.isValid + return response.data?.isAlreadyExist } catch { os_log(.error, log: .data, "%@", error.localizedDescription) return nil @@ -107,7 +107,7 @@ final class MockLoginWorker: LoginWorkerProtocol { method: .POST, bodyParameters: bodyParameters) let response = try await provider.request(with: endPoint, authenticationIfNeeded: false, retryCount: 0) - return response.data?.isValid + return response.data?.isAlreadyExist } catch { os_log(.error, log: .data, "%@", error.localizedDescription) return nil From b84fc898e7cc3bdea7fdb35c0ae97da3717d6f5b Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 6 Dec 2023 16:56:22 +0900 Subject: [PATCH 22/25] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/Notification.Name+.swift | 1 + iOS/Layover/Layover/SceneDelegate.swift | 19 ++++++++--- .../UploadPost/UploadPostInteractor.swift | 32 +++++++++++-------- .../Scenes/UploadPost/UploadPostWorker.swift | 6 ++-- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/iOS/Layover/Layover/Extensions/Notification.Name+.swift b/iOS/Layover/Layover/Extensions/Notification.Name+.swift index bec37cf..ebb5e37 100644 --- a/iOS/Layover/Layover/Extensions/Notification.Name+.swift +++ b/iOS/Layover/Layover/Extensions/Notification.Name+.swift @@ -12,4 +12,5 @@ extension Notification.Name { static let refreshTokenDidExpired = Notification.Name("refreshTokenDidExpired") static let uploadTaskStart = Notification.Name("uploadTaskStart") static let progressChanged = Notification.Name("progressChanged") + static let uploadTaskDidComplete = Notification.Name("uploadTaskDidComplete") } diff --git a/iOS/Layover/Layover/SceneDelegate.swift b/iOS/Layover/Layover/SceneDelegate.swift index fb149ad..1f06329 100644 --- a/iOS/Layover/Layover/SceneDelegate.swift +++ b/iOS/Layover/Layover/SceneDelegate.swift @@ -72,13 +72,17 @@ extension SceneDelegate { name: .refreshTokenDidExpired, object: nil) NotificationCenter.default.addObserver(self, - selector: #selector(uploadTaskStart), + selector: #selector(showProgressView), name: .uploadTaskStart, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressChanged), name: .progressChanged, object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(removeProgressView), + name: .uploadTaskDidComplete, + object: nil) } private func removeNotificationObservers() { @@ -91,16 +95,19 @@ extension SceneDelegate { NotificationCenter.default.removeObserver(self, name: .progressChanged, object: nil) + NotificationCenter.default.removeObserver(self, + name: .uploadTaskDidComplete, + object: nil) } @objc private func routeToLoginViewController() { guard let rootNavigationViewController = window?.rootViewController as? UINavigationController else { return } - // TODO: 세션이 만료되었습니다. Toast 띄우기 + Toast.shared.showToast(message: "세션이 만료되었습니다.") rootNavigationViewController.setNavigationBarHidden(false, animated: false) rootNavigationViewController.setViewControllers([LoginViewController()], animated: true) } - @objc private func uploadTaskStart() { + @objc private func showProgressView() { guard let progressViewWidth = window?.screen.bounds.width, let windowHeight = window?.screen.bounds.height, let tabBarViewController = window?.rootViewController as? UITabBarController else { return } @@ -119,9 +126,13 @@ extension SceneDelegate { guard let progress = notification.userInfo?["progress"] as? Float else { return } progressView.setProgress(progress, animated: true) if progress == 1 { - progressView.removeFromSuperview() Toast.shared.showToast(message: "업로드가 완료되었습니다 ✨") } } + + @objc private func removeProgressView() { + progressView.removeFromSuperview() + } + } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 37aebaf..85c3f9a 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -14,7 +14,10 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() - func fetchThumbnailImage() + + @discardableResult + func fetchThumbnailImage() -> Task + func fetchCurrentAddress() func canUploadPost(request: UploadPostModels.CanUploadPost.Request) @@ -61,26 +64,30 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD presenter?.presentTags(with: Models.FetchTags.Response(tags: tags)) } - func fetchThumbnailImage() { - guard let videoURL else { return } + @discardableResult + func fetchThumbnailImage() -> Task { + Task { + guard let videoURL else { return false } let asset = AVAsset(url: videoURL) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true - Task { do { let image = try await generator.image(at: .zero).image await MainActor.run { presenter?.presentThumnailImage(with: Models.FetchThumbnail.Response(thumnailImage: image)) } + return true } catch let error { os_log(.error, log: .default, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) + return false } } } func fetchCurrentAddress() { guard let location = getCurrentLocation() else { return } - let locale = Locale(identifier: "ko_KR") + let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier + let locale = Locale(identifier: localeIdentifier) Task { do { @@ -108,6 +115,9 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD @discardableResult func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task { Task { + await MainActor.run { + NotificationCenter.default.post(name: .uploadTaskStart, object: nil) + } guard let worker, let videoURL, let isMuted, @@ -115,15 +125,15 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD 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)) + await MainActor.run { + NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) + } return response } } @@ -182,12 +192,8 @@ extension UploadPostInteractor: URLSessionTaskDelegate { DispatchQueue.main.async { NotificationQueue.default.enqueue(Notification(name: .progressChanged, userInfo: ["progress": uploadProgress]), - postingStyle: .now) + postingStyle: .asap) } } - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - // TODO: 완료 토스트 - - } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 5ff3354..3350a95 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -57,9 +57,9 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { do { let response = try await provider.request(with: endPoint) guard let preSignedURLString = response.data?.preSignedURL else { return false } - let data = try await provider.backgroundUpload(fromFile: videoURL, - to: preSignedURLString, - sessionTaskDelegate: sessionTaskDelegate) + _ = try await provider.backgroundUpload(fromFile: videoURL, + to: preSignedURLString, + sessionTaskDelegate: sessionTaskDelegate) return true } catch { os_log(.error, log: .data, "Failed to upload Video: %@", error.localizedDescription) From c79df362f38385fa3d6965514d602aa71e8ff446 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 6 Dec 2023 17:13:43 +0900 Subject: [PATCH 23/25] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20session=20delegate?= =?UTF-8?q?=EB=A5=BC=20worker=EA=B0=80=20=EC=B1=84=ED=83=9D,=20background?= =?UTF-8?q?=20upload=EB=A5=BC=20upload=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Network/Provider/Provider.swift | 23 ++++++------ .../UploadPost/UploadPostConfigurator.swift | 1 - .../UploadPost/UploadPostInteractor.swift | 25 ------------- .../Scenes/UploadPost/UploadPostWorker.swift | 37 +++++++++++++++++-- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index eec9b1c..3f01d1f 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -13,7 +13,7 @@ protocol ProviderType { func request(url: URL) async throws -> Data func request(url: String) async throws -> Data func upload(data: Data, to url: String, method: HTTPMethod) async throws -> Data - func backgroundUpload(fromFile: URL, + func upload(fromFile: URL, to url: String, method: HTTPMethod, sessionTaskDelegate: URLSessionTaskDelegate?, @@ -35,12 +35,12 @@ extension ProviderType { method: method) } - func backgroundUpload(fromFile: URL, + func upload(fromFile: URL, to url: String, method: HTTPMethod = .PUT, sessionTaskDelegate: URLSessionTaskDelegate? = nil, delegateQueue: OperationQueue? = nil) async throws -> Data { - return try await backgroundUpload(fromFile: fromFile, + return try await upload(fromFile: fromFile, to: url, method: method, sessionTaskDelegate: sessionTaskDelegate, @@ -137,19 +137,18 @@ class Provider: ProviderType { } // 동영상 업로드용 - func backgroundUpload(fromFile: URL, - to url: String, - method: HTTPMethod = .PUT, - sessionTaskDelegate: URLSessionTaskDelegate? = nil, - delegateQueue: OperationQueue? = nil) async throws -> Data { + func upload(fromFile: URL, + to url: String, + method: HTTPMethod = .PUT, + sessionTaskDelegate: URLSessionTaskDelegate? = nil, + delegateQueue: OperationQueue? = nil) async throws -> Data { guard let url = URL(string: url) else { throw NetworkError.components } var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) request.httpMethod = method.rawValue 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) + let (data, response) = try await session.upload(for: request, + fromFile: fromFile, + delegate: sessionTaskDelegate) try self.checkStatusCode(of: response) return data } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift index 85794a6..a31a522 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift @@ -23,7 +23,6 @@ final class UploadPostConfigurator: Configurator { let router = UploadPostRouter() let worker = UploadPostWorker() - worker.sessionTaskDelegate = interactor router.viewController = viewController router.dataStore = interactor viewController.interactor = interactor diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 85c3f9a..b3d7fdf 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -115,9 +115,6 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD @discardableResult func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task { Task { - await MainActor.run { - NotificationCenter.default.post(name: .uploadTaskStart, object: nil) - } guard let worker, let videoURL, let isMuted, @@ -131,9 +128,6 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD longitude: coordinate.longitude, tag: request.tags, videoURL: videoURL)) - await MainActor.run { - NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) - } return response } } @@ -178,22 +172,3 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD } } - -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: .asap) - } - } - -} diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 3350a95..b57c016 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -22,8 +22,6 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { private let provider: ProviderType private let uploadPostEndPointFactory: UploadPostEndPointFactory - weak var sessionTaskDelegate: URLSessionTaskDelegate? - // MARK: - Methods init(provider: ProviderType = Provider(), @@ -57,14 +55,45 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { do { let response = try await provider.request(with: endPoint) guard let preSignedURLString = response.data?.preSignedURL else { return false } - _ = try await provider.backgroundUpload(fromFile: videoURL, + _ = try await provider.upload(fromFile: videoURL, to: preSignedURLString, - sessionTaskDelegate: sessionTaskDelegate) + sessionTaskDelegate: self) + await MainActor.run { + NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) + } return true } catch { os_log(.error, log: .data, "Failed to upload Video: %@", error.localizedDescription) + await MainActor.run { + NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) + } return false } } } + +extension UploadPostWorker: URLSessionTaskDelegate { + + func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { + DispatchQueue.main.async { + NotificationCenter.default.post(name: .uploadTaskStart, object: nil) + } + } + + 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: .asap) + } + } + +} From 218bf2441408d71894dbcb8f570c38a16c3bdbd1 Mon Sep 17 00:00:00 2001 From: chopmozzi <44396392+chopmozzi@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:55:53 +0900 Subject: [PATCH 24/25] =?UTF-8?q?:recycle:=20=EB=A6=AC=EB=B7=B0=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=812?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 유경님 리뷰사항 반영 --- iOS/Layover/Layover/DesignSystem/LOReportStackView.swift | 4 ++-- iOS/Layover/Layover/Network/DTOs/ReportDTO.swift | 2 +- .../Network/EndPoint/Factories/ReportEndPointFactory.swift | 6 +++--- iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift | 4 ++-- iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift | 6 +++--- iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift | 2 +- iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift | 2 +- iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift | 2 +- iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift | 6 +----- iOS/Layover/Layover/Scenes/Report/ReportWorker.swift | 6 +++--- iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift | 4 ++-- 11 files changed, 20 insertions(+), 24 deletions(-) diff --git a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift index daa7676..309e686 100644 --- a/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift +++ b/iOS/Layover/Layover/DesignSystem/LOReportStackView.swift @@ -56,8 +56,8 @@ final class LOReportStackView: UIStackView { self.reportViews.forEach { reportView in if reportView.index == tempView.index { reportView.onRadio() - guard let reportContentStr: String = reportView.contentLabel.text else { return } - reportContent = reportContentStr + guard let reportContentText: String = reportView.contentLabel.text else { return } + reportContent = reportContentText } else { reportView.offRadio() } diff --git a/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift b/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift index d2d5dbf..8588bd8 100644 --- a/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/ReportDTO.swift @@ -10,6 +10,6 @@ import Foundation struct ReportDTO: Codable { let memberId: Int? - let boardId: Int + let boardID: Int let reportType: String } diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift index a87aa1e..ac01e83 100644 --- a/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/ReportEndPointFactory.swift @@ -9,14 +9,14 @@ import Foundation protocol ReportEndPointFactory { - func reportPlaybackVideoEndpoint(boardId: Int, reportType: String) -> EndPoint> + func reportPlaybackVideoEndpoint(boardID: Int, reportType: String) -> EndPoint> } struct DefaultReportEndPointFactory: ReportEndPointFactory { - func reportPlaybackVideoEndpoint(boardId: Int, reportType: String) -> EndPoint> { + func reportPlaybackVideoEndpoint(boardID: Int, reportType: String) -> EndPoint> { var bodyParmeters: ReportDTO = ReportDTO( memberId: nil, - boardId: boardId, + boardID: boardID, reportType: reportType) return EndPoint( diff --git a/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift b/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift index ba9c0ce..8eaf296 100644 --- a/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift +++ b/iOS/Layover/Layover/Scenes/Playback/Cell/PlaybackCell.swift @@ -10,7 +10,7 @@ import UIKit final class PlaybackCell: UICollectionViewCell { let playbackView: PlaybackView = PlaybackView() - var boardId: Int = 0 + var boardID: Int = 0 override init(frame: CGRect) { super.init(frame: frame) @@ -23,7 +23,7 @@ final class PlaybackCell: UICollectionViewCell { } func setPlaybackContents(info: PlaybackModels.PlaybackInfo) { - boardId = info.boardId + boardID = info.boardID playbackView.descriptionView.titleLabel.text = info.title playbackView.descriptionView.setText(info.content) playbackView.profileLabel.text = info.profileName diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift index 958e975..cf19887 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackModels.swift @@ -21,7 +21,7 @@ enum PlaybackModels { } struct PlaybackInfo: Hashable { - let boardId: Int + let boardID: Int let title: String let content: String let profileImageURL: URL? @@ -137,11 +137,11 @@ enum PlaybackModels { } struct Response { - let boardId: Int + let boardID: Int } struct ViewModel { - let boardId: Int + let boardID: Int } } } diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift index 098b29b..73a605e 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackPresenter.swift @@ -35,7 +35,7 @@ final class PlaybackPresenter: PlaybackPresentationLogic { return nil } return Models.PlaybackVideo(playbackInfo: PlaybackModels.PlaybackInfo( - boardId: post.board.identifier, + boardID: post.board.identifier, title: post.board.title, content: post.board.description ?? "", profileImageURL: post.member.profileImageURL, diff --git a/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift b/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift index d71af8b..24c142b 100644 --- a/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift +++ b/iOS/Layover/Layover/Scenes/Playback/PlaybackRouter.swift @@ -30,7 +30,7 @@ final class PlaybackRouter: NSObject, PlaybackRoutingLogic, PlaybackDataPassing guard let source = dataStore, var destination = reportViewController.router?.dataStore else { return } - destination.boardID = source.prevCell?.boardId + destination.boardID = source.prevCell?.boardID reportViewController.modalPresentationStyle = .fullScreen viewController?.present(reportViewController, animated: false) } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift index cf71b29..5af9433 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportInteractor.swift @@ -33,7 +33,7 @@ final class ReportInteractor: ReportBusinessLogic, ReportDataStore { guard let boardID, let worker else { return false } - let result: Bool = await worker.reportPlaybackVideo(boardId: boardID, reportContent: request.reportContent) + let result: Bool = await worker.reportPlaybackVideo(boardID: boardID, reportContent: request.reportContent) let response: Models.ReportPlaybackVideo.Response = Models.ReportPlaybackVideo.Response(reportResult: result) await MainActor.run { presenter?.presentReportPlaybackVideo(with: response) diff --git a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift index 906fba0..7133322 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportPresenter.swift @@ -21,11 +21,7 @@ final class ReportPresenter: ReportPresentationLogic { func presentReportPlaybackVideo(with response: ReportModels.ReportPlaybackVideo.Response) { let viewModel: Models.ReportPlaybackVideo.ViewModel - if response.reportResult { - viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: .success) - } else { - viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: .fail) - } + viewModel = Models.ReportPlaybackVideo.ViewModel(reportMessage: response.reportResult ? .success : .fail) viewController?.displayReportResult(viewModel: viewModel) } } diff --git a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift index 6abd901..67d9d34 100644 --- a/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift +++ b/iOS/Layover/Layover/Scenes/Report/ReportWorker.swift @@ -11,7 +11,7 @@ import Foundation import OSLog protocol ReportWorkerProtocol { - func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool + func reportPlaybackVideo(boardID: Int, reportContent: String) async -> Bool } final class ReportWorker: ReportWorkerProtocol { @@ -28,8 +28,8 @@ final class ReportWorker: ReportWorkerProtocol { self.provider = provider } - func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool { - let endPoint = reportEndPointFactory.reportPlaybackVideoEndpoint(boardId: boardId, reportType: reportContent) + func reportPlaybackVideo(boardID: Int, reportContent: String) async -> Bool { + let endPoint = reportEndPointFactory.reportPlaybackVideoEndpoint(boardID: boardID, reportType: reportContent) do { let responseData = try await provider.request(with: endPoint) guard let _ = responseData.data else { diff --git a/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift index 27cda19..801a268 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockReportWorker.swift @@ -17,7 +17,7 @@ final class MockReportWorker: ReportWorkerProtocol { // MARK: - Methods - func reportPlaybackVideo(boardId: Int, reportContent: String) async -> Bool { + func reportPlaybackVideo(boardID: Int, reportContent: String) async -> Bool { guard let mockFileLocation = Bundle.main.url(forResource: "ReportPlaybackVideo", withExtension: "json"), let mockData = try? Data(contentsOf: mockFileLocation) else { @@ -36,7 +36,7 @@ final class MockReportWorker: ReportWorkerProtocol { do { let bodyParameters = ReportDTO( memberId: nil, - boardId: 1, + boardID: 1, reportType: "청소년에게 유해한 내용입니다.") let endPoint = EndPoint>(path: "/report", method: .POST, From 8f7cb82ee413bf9b6401f1c4bc069740d4669a57 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 6 Dec 2023 18:28:12 +0900 Subject: [PATCH 25/25] =?UTF-8?q?=F0=9F=94=A7=20SceneDelegate=20rootVC=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/SceneDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/SceneDelegate.swift b/iOS/Layover/Layover/SceneDelegate.swift index 1f06329..ac4b83c 100644 --- a/iOS/Layover/Layover/SceneDelegate.swift +++ b/iOS/Layover/Layover/SceneDelegate.swift @@ -20,7 +20,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let window = UIWindow(windowScene: windowScene) let rootViewController = AuthManager.shared.isLoggedIn ? MainTabBarViewController() : LoginViewController() - window.rootViewController = MainTabBarViewController() + window.rootViewController = UINavigationController(rootViewController: rootViewController) self.window = window addNotificationObservers() window.makeKeyAndVisible()