diff --git a/App/App/AppDelegate.swift b/App/App/AppLifeCycles/AppDelegate.swift similarity index 100% rename from App/App/AppDelegate.swift rename to App/App/AppLifeCycles/AppDelegate.swift diff --git a/App/App/AppLifeCycles/SceneDelegate.swift b/App/App/AppLifeCycles/SceneDelegate.swift new file mode 100644 index 00000000..71421e77 --- /dev/null +++ b/App/App/AppLifeCycles/SceneDelegate.swift @@ -0,0 +1,37 @@ +// +// SceneDelegate.swift +// App +// +// Created by jung on 11/4/24. +// + +import Core +import Data +import Feature +import Interfaces +import P2PSocket +import UIKit +import UseCase + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + let container = MainContainer() + var coordinator: Coordinator? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = (scene as? UIWindowScene) else { return } + + let coordinator = container.coordinator() + self.coordinator = coordinator + coordinator.start(nil) + let window = UIWindow(windowScene: windowScene) + window.rootViewController = coordinator.viewController + + self.window = window + window.makeKeyAndVisible() + } +} diff --git a/App/App/MainContainer.swift b/App/App/MainContainer.swift new file mode 100644 index 00000000..a89d94ed --- /dev/null +++ b/App/App/MainContainer.swift @@ -0,0 +1,83 @@ +// +// MainContainer.swift +// App +// +// Created by jung on 12/6/24. +// + +import Core +import Feature +import P2PSocket +import UseCase +import Data +import Interfaces + +final class MainContainer: + ConnectionDependency, + VideoListDependency, + GroupInfoDependency, + SharedEditVideoDependency, + PreviewDependency { + let socketProvider = SocketProvider() + + func coordinator() -> Coordinator { + let groupInfoContainer = GroupInfoContainer(dependency: self) + let connectionContainer = ConnectionContainer(dependency: self) + + return MainCoordinator( + groupInfoContainer: groupInfoContainer, + connectionContainer: connectionContainer + ) + } + + // MARK: - Containable + var videoListContaiable: VideoListContainable { + return VideoListContainer(dependency: self) + } + + var sharedEditVideoContainer: SharedEditVideoContainable { + return SharedVideoEditContainer(dependency: self) + } + + var previewContainer: PreviewContainable { + return PreviewContainer(dependency: self) + } + + // MARK: - Repository + var browsingUserRepository: BrowsingUserRepositoryInterface { + return BrowsingUserRepository(socketProvider: socketProvider) + } + + var connectedUserRepository: ConnectedUserRepositoryInterface { + return ConnectedUserRepository(socketProvider: socketProvider) + } + + var sharingVideoRepository: SharingVideoRepositoryInterface { + return SharingVideoRepository(socketProvider: socketProvider) + } + + var editVideoRepository: EditVideoRepositoryInterface { + return EditVideoRepository(socketProvider: socketProvider) + } + + // MARK: - UseCase + var browsingUseCase: BrowsingUserUseCaseInterface { + return BrowsingUserUseCase(repository: browsingUserRepository) + } + + var connectedUserUseCase: ConnectedUserUseCaseInterface { + return ConnectedUserUseCase(repository: connectedUserRepository) + } + + lazy var videoUseCase: VideoUseCase = { + return VideoUseCase(sharingVideoRepository: sharingVideoRepository, editVideoRepository: editVideoRepository) + }() + + var sharingVideoUseCase: SharingVideoUseCaseInterface { + videoUseCase + } + + var editVideoUseCase: EditVideoUseCaseInterface { + videoUseCase + } +} diff --git a/App/App/MainCoordinator.swift b/App/App/MainCoordinator.swift new file mode 100644 index 00000000..db57472f --- /dev/null +++ b/App/App/MainCoordinator.swift @@ -0,0 +1,73 @@ +// +// MainCoordinator.swift +// App +// +// Created by jung on 12/6/24. +// + +import Core +import Feature +import UIKit + +public final class MainCoordinator: Coordinator { + private var bottomNavigationController: UINavigationController? + + // MARK: - Coordinatables + private let groupInfoContainer: GroupInfoContainable + private var groupInfoCoordinatable: Coordinatable? + + private let connectionContainer: ConnectionContainable + private var connectionCoordinatable: Coordinatable? + + // MARK: - Initializer + public init( + groupInfoContainer: GroupInfoContainable, + connectionContainer: ConnectionContainable + ) { + self.groupInfoContainer = groupInfoContainer + self.connectionContainer = connectionContainer + super.init(viewController: MainViewController()) + } + + public override func start(_ navigationController: UINavigationController?) { + guard let viewController = viewController as? MainViewController else { return } + + let topViewController = groupInfoViewController() + let bottomViewController = connectionViewController() + viewController.attachTopViewController(topViewController) + viewController.attachBottomViewController(bottomViewController) + } +} + +private extension MainCoordinator { + func connectionViewController() -> UIViewController { + let coordinator = connectionContainer.coordinator(listener: self) + connectionCoordinatable = coordinator + let navigation = UINavigationController(rootViewController: coordinator.viewController) + bottomNavigationController = navigation + coordinator.start(navigation) + + return navigation + } + + func groupInfoViewController() -> UIViewController { + let coordinator = groupInfoContainer.coordinator(listener: self) + groupInfoCoordinatable = coordinator + let navigation = UINavigationController(rootViewController: coordinator.viewController) + coordinator.start(navigation) + + return navigation + } +} + +// MARK: - ConnectionListener +extension MainCoordinator: ConnectionListener { } + +// MARK: - GroupInfoListener +extension MainCoordinator: GroupInfoListener { + public func exitGroupButtonDidTap() { + FileSystemManager.shared.deleteAllFiles() + bottomNavigationController?.popToRootViewController(animated: true) + connectionCoordinatable?.start(bottomNavigationController) + } +} diff --git a/App/App/MainViewController.swift b/App/App/MainViewController.swift new file mode 100644 index 00000000..1ad3f882 --- /dev/null +++ b/App/App/MainViewController.swift @@ -0,0 +1,40 @@ +// +// MainViewController.swift +// App +// +// Created by jung on 12/6/24. +// + +import SnapKit +import UIKit + +final class MainViewController: UIViewController { + private enum Constants { + static let topViewHeight: CGFloat = 200 + } + + func attachTopViewController(_ viewController: UIViewController) { + addChild(viewController) + view.addSubview(viewController.view) + viewController.didMove(toParent: self) + viewController.view.backgroundColor = .red + viewController.view.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide) + make.leading.equalToSuperview() + make.trailing.equalToSuperview() + make.height.equalTo(Constants.topViewHeight) + } + } + + func attachBottomViewController(_ viewController: UIViewController) { + addChild(viewController) + view.addSubview(viewController.view) + viewController.didMove(toParent: self) + + viewController.view.snp.makeConstraints { make in + make.top.equalToSuperview().offset(Constants.topViewHeight) + make.leading.trailing.equalToSuperview() + make.bottom.equalTo(view.safeAreaLayoutGuide) + } + } +} diff --git a/App/App/RootViewController.swift b/App/App/RootViewController.swift deleted file mode 100644 index 9c66d31d..00000000 --- a/App/App/RootViewController.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RootViewController.swift -// App -// -// Created by jung on 11/4/24. -// - -import UIKit - -final class RootViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - print(#function) - } -} diff --git a/App/App/SceneDelegate.swift b/App/App/SceneDelegate.swift deleted file mode 100644 index d69d7e1f..00000000 --- a/App/App/SceneDelegate.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// SceneDelegate.swift -// App -// -// Created by jung on 11/4/24. -// - -import Core -import Data -import Feature -import Interfaces -import P2PSocket -import UIKit -import UseCase - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } - registerDependency() - - let window = UIWindow(windowScene: windowScene) - - let groupInfoViewController = GroupInfoViewController(viewModel: DIContainer.shared.resolve(type: GroupInfoViewModel.self)) - let connectionViewController = ConnectionViewController(viewModel: DIContainer.shared.resolve(type: ConnectionViewModel.self)) - - window.rootViewController = MainViewController( - topViewController: groupInfoViewController, - initialViewController: connectionViewController - ) - - self.window = window - window.makeKeyAndVisible() - } -} - -extension SceneDelegate { - func registerDependency() { - registerP2PSocket() - registerRepository() - registerUseCase() - registerViewModel() - } - - func registerP2PSocket() { - DIContainer.shared.register( - type: SocketProvidable.self, - instance: SocketProvider() - ) - } - - func registerRepository() { - let socketProvider = DIContainer.shared.resolve(type: SocketProvidable.self) - - DIContainer.shared.register( - type: BrowsingUserRepositoryInterface.self, - instance: BrowsingUserRepository(socketProvider:socketProvider) - ) - - DIContainer.shared.register( - type: ConnectedUserRepositoryInterface.self, - instance: ConnectedUserRepository(socketProvider: socketProvider) - ) - - DIContainer.shared.register( - type: SharingVideoRepositoryInterface.self, - instance: SharingVideoRepository(socketProvider: socketProvider) - ) - - DIContainer.shared.register( - type: EditVideoRepositoryInterface.self, - instance: EditVideoRepository(socketProvider: socketProvider) - ) - } - - func registerUseCase() { - DIContainer.shared.register( - type: BrowsingUserUseCaseInterface.self, - instance: BrowsingUserUseCase( - repository: DIContainer.shared.resolve(type: BrowsingUserRepositoryInterface.self) - ) - ) - - DIContainer.shared.register( - type: ConnectedUserUseCaseInterface.self, - instance: ConnectedUserUseCase( - repository: DIContainer.shared.resolve(type: ConnectedUserRepositoryInterface.self) - ) - ) - - DIContainer.shared.register( - type: VideoUseCase.self, - instance: VideoUseCase( - sharingVideoRepository: DIContainer.shared.resolve(type: SharingVideoRepositoryInterface.self), - editVideoRepository: DIContainer.shared.resolve(type: EditVideoRepositoryInterface.self) - ) - ) - - DIContainer.shared.register( - type: SharingVideoUseCaseInterface.self, - instance: DIContainer.shared.resolve(type: VideoUseCase.self) - ) - - DIContainer.shared.register( - type: EditVideoUseCaseInterface.self, - instance: DIContainer.shared.resolve(type: VideoUseCase.self) - ) - } - - func registerViewModel() { - DIContainer.shared.register( - type: ConnectionViewModel.self, - instance: ConnectionViewModel( - usecase: DIContainer.shared.resolve(type: BrowsingUserUseCaseInterface.self) - ) - ) - - DIContainer.shared.register( - type: GroupInfoViewModel.self, - instance: GroupInfoViewModel( - usecase: DIContainer.shared.resolve(type: ConnectedUserUseCaseInterface.self) - ) - ) - - DIContainer.shared.register( - type: MultipeerVideoListViewModel.self, - instance: MultipeerVideoListViewModel( - usecase: DIContainer.shared.resolve(type: SharingVideoUseCaseInterface.self) - ) - ) - - DIContainer.shared.register( - type: SharedVideoEditViewModel.self, - instance: SharedVideoEditViewModel( - usecase: DIContainer.shared.resolve(type: EditVideoUseCaseInterface.self) - ) - ) - - DIContainer.shared.register( - type: PreviewViewModel.self, - instance: PreviewViewModel(usecase: DIContainer.shared.resolve(type: EditVideoUseCaseInterface.self)) - ) - } -} diff --git a/Core/Utilities/BaseCoordinator/Container.swift b/Core/Utilities/BaseCoordinator/Container.swift new file mode 100644 index 00000000..aa6c75b7 --- /dev/null +++ b/Core/Utilities/BaseCoordinator/Container.swift @@ -0,0 +1,15 @@ +// +// Container.swift +// Core +// +// Created by jung on 12/6/24. +// + +/// `Coordinator`, `ViewController`, `Interactor`에서 필요한 의존성을 들고 있으며, `Coordinator`생성을 담당하는 객체입니다. +open class Container { + public let dependency: DependencyType + + public init(dependency: DependencyType) { + self.dependency = dependency + } +} diff --git a/Core/Utilities/BaseCoordinator/Coordinator.swift b/Core/Utilities/BaseCoordinator/Coordinator.swift new file mode 100644 index 00000000..0d8e0e24 --- /dev/null +++ b/Core/Utilities/BaseCoordinator/Coordinator.swift @@ -0,0 +1,68 @@ +// +// Coordinator.swift +// Core +// +// Created by jung on 12/5/24. +// + +import UIKit + +/// 부모 Coordinator에게 요청하는 메서드를 구현하면 됩니다. +public protocol CoordinatorListener: AnyObject { } + +/// 화면 전환 로직 및, `ViewController`와 `Interactor`생성을 담당하는 객체입니다. +public protocol Coordinatable: AnyObject { + var viewController: UIViewController { get } + var navigationController: UINavigationController? { get set } + var children: [Coordinatable] { get } + + func start(_ navigationController: UINavigationController?) + func stop() + func addChild(_ coordinator: Coordinatable) + func removeChild(_ coordinator: Coordinatable) +} + +open class Coordinator: Coordinatable { + public let viewController: UIViewController + public var navigationController: UINavigationController? + public final var children: [Coordinatable] = [] + + public init(viewController: UIViewController) { + self.viewController = viewController + } + + /// 부모에게 붙여졌을 때, 원하는 동작을 해당 메서드에 구현합니다. + open func start(_ navigationController: UINavigationController?) { + self.navigationController = navigationController + } + + /// 부모에게 제거되었을 때 원하는 동작을 해당 메서드에 구현합니다. + open func stop() { + navigationController?.popViewController(animated: true) + navigationController = nil + } + + public final func addChild(_ coordinator: Coordinatable) { + guard !children.contains(where: { $0 === coordinator }) else { return } + + children.append(coordinator) + } + + public final func removeChild(_ coordinator: Coordinatable) { + guard let index = children.firstIndex(where: { $0 === coordinator }) else { return } + + children.remove(at: index) + } + + deinit { + self.stop() + if !children.isEmpty { removeAllChild() } + } +} + +// MARK: - Private Methods +private extension Coordinator { + func removeAllChild() { + children.forEach { removeChild($0) } + } +} diff --git a/Core/Utilities/DIContainer.swift b/Core/Utilities/DIContainer.swift deleted file mode 100644 index 8095a1de..00000000 --- a/Core/Utilities/DIContainer.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// DIContainer.swift -// Core -// -// Created by 이숲 on 11/20/24. -// - -public final class DIContainer { - public static let shared = DIContainer() - - private var services: [String: Any] = [:] - - private init() { } - - public func register(type: T.Type, instance: T) { - let key = String(describing: type) - services[key] = instance - } - - public func resolve(type: T.Type) -> T { - let key = String(describing: type) - guard let service = services[key] as? T else { - fatalError("\(key) is not registered.") - } - return service - } -} diff --git a/Data/CRDTTest/CRDTTest.swift b/Data/CRDTTest/CRDTTest.swift index a705caa8..cec35a73 100644 --- a/Data/CRDTTest/CRDTTest.swift +++ b/Data/CRDTTest/CRDTTest.swift @@ -31,7 +31,6 @@ final class CRDTTest: XCTestCase { } func testIsMergeCompletWhenEventArrivedRandomly() async { - let peerCount = 3 let peer0ID = UUID().uuidString let peer1ID = UUID().uuidString let peer2ID = UUID().uuidString diff --git a/Data/Data/DataMapper/DataMapper.swift b/Data/Data/DataMapper/DataMapper.swift index 023fcef6..4714c8c7 100644 --- a/Data/Data/DataMapper/DataMapper.swift +++ b/Data/Data/DataMapper/DataMapper.swift @@ -69,7 +69,10 @@ enum DataMapper { } static func mappingToVideo(_ element: EditVideoElement) -> Video? { - guard let url = FileSystemManager.shared.mappingToLocalURL(url: element.url, resourceName: element.name) else { return nil } + guard let url = FileSystemManager.shared.mappingToLocalURL( + url: element.url, + resourceName: element.name + ) else { return nil } let user = DataMapper.mappingToConnectedUser(element.editor) return .init( diff --git a/Data/Data/EditVideoRepository.swift b/Data/Data/EditVideoRepository.swift index 85d20583..069cd4fe 100644 --- a/Data/Data/EditVideoRepository.swift +++ b/Data/Data/EditVideoRepository.swift @@ -19,7 +19,7 @@ public final class EditVideoRepository: EditVideoRepositoryInterface { // MARK: - Properties private var cancellables: Set = [] private let socketProvider: EditVideoSocketProvidable - private var elementSet: LWWElementSet + private let elementSet: LWWElementSet public let editedVideos = PassthroughSubject<[Video], Never>() @@ -74,28 +74,20 @@ public extension EditVideoRepository { // MARK: - Binding private extension EditVideoRepository { func binding() { - socketProvider.updatedPeer.sink(with: self) { owner, _ in - let id = owner.socketProvider.id - let peerIDs = owner.socketProvider.connectedPeers().map { $0.id } - owner.elementSet = .init(id: id, peerIDs: peerIDs) - owner.bindUpdatedElements() - }.store(in: &cancellables) - - socketProvider.dataShared - .sink(with: self) { owner, data in - owner.merge(data: data.0) - } - .store(in: &cancellables) - } - - func bindUpdatedElements() { Task { await elementSet.updatedElements .sink(with: self) { owner, elements in + print(elements.count) owner.sendVideo(elements: elements) } .store(in: &cancellables) } + + socketProvider.dataShared + .sink(with: self) { owner, data in + owner.merge(data: data.0) + } + .store(in: &cancellables) } } diff --git a/Data/Data/SharingVideoRepository.swift b/Data/Data/SharingVideoRepository.swift index c8c4dc40..703a3ba7 100644 --- a/Data/Data/SharingVideoRepository.swift +++ b/Data/Data/SharingVideoRepository.swift @@ -34,8 +34,8 @@ public final class SharingVideoRepository: SharingVideoRepositoryInterface { public init(socketProvider: SharingVideoSocketProvidable) { self.socketProvider = socketProvider socketProvider.resourceShared - .compactMap { resource in - DataMapper.mappingToSharedVideo(resource) + .compactMap { + return .init(localUrl: $0.url, name: $0.name, author: "iPhone") } .subscribe(updatedSharedVideo) .store(in: &cancellables) diff --git a/Domain/UseCase/VideoUseCase.swift b/Domain/UseCase/VideoUseCase.swift index 3eb75f18..ec26e264 100644 --- a/Domain/UseCase/VideoUseCase.swift +++ b/Domain/UseCase/VideoUseCase.swift @@ -25,7 +25,7 @@ public final class VideoUseCase { public let editedVideos = PassthroughSubject<[Video], Never>() public var videos: [Video] { - editingVideos.values.sorted(by: { $0.index < $1.index }) + editingVideos.sorted(by: { $0.index < $1.index }) } public init( @@ -123,12 +123,6 @@ private extension VideoUseCase { .store(in: &cancellables) } - func updateEditingVideos(_ videos: [Video]) { - videos.forEach { - editingVideos[$0.url.path] = $0 - } - } - func updatedVideo(url: URL, startTime: Double, endTime: Double) -> Video? { guard let video = editingVideos.first(where: { $0.url.path == url.path }) else { return nil } @@ -159,7 +153,7 @@ private extension VideoUseCase { } editingVideos = listIndexOrderedVideo - return newVideos + return editingVideos } func updatedVideo(video: Video, index: Int) -> Video { diff --git a/Feature/Feature.xcodeproj/project.pbxproj b/Feature/Feature.xcodeproj/project.pbxproj index ab8b2f08..34b4c37d 100644 --- a/Feature/Feature.xcodeproj/project.pbxproj +++ b/Feature/Feature.xcodeproj/project.pbxproj @@ -37,11 +37,15 @@ D95A3B062D01D7500007A71A /* Exceptions for "Feature" folder in "Feature" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + ConnectionView/ConnectionCoordinator.swift, + ConnectionView/ConnectionViewContainer.swift, ConnectionView/ConnectionViewController.swift, ConnectionView/View/CustomCircleView.swift, ConnectionView/ViewModel/ConnectionViewInput.swift, ConnectionView/ViewModel/ConnectionViewModel.swift, ConnectionView/ViewModel/ConnectionViewOutput.swift, + GroupInfoView/GroupInfoContainer.swift, + GroupInfoView/GroupInfoCoordinator.swift, GroupInfoView/GroupInfoViewController.swift, GroupInfoView/View/ParticipantCountView.swift, GroupInfoView/View/ParticipantInfoView.swift, @@ -64,13 +68,16 @@ "Helper/UIImage+convertColor.swift", "Helper/UIImage+resize.swift", "Helper/UIView+Extension.swift", - MainViewController.swift, PresentationModel/VideoPresentationModel.swift, + PreviewView/PreviewContainer.swift, + PreviewView/PreviewCoordinator.swift, PreviewView/PreviewViewController.swift, PreviewView/ViewModel/PreviewViewInput.swift, PreviewView/ViewModel/PreviewViewModel.swift, PreviewView/ViewModel/PreviewViewOutput.swift, ResultView/ResultViewController.swift, + SharedVideoEditView/SharedVideoEditContainer.swift, + SharedVideoEditView/SharedVideoEditCoordinator.swift, SharedVideoEditView/SharedVideoEditViewController.swift, SharedVideoEditView/VideoMerger/VideoMerger.swift, SharedVideoEditView/View/CollectionView/VideoTimelineCollectionViewCell.swift, @@ -88,8 +95,9 @@ SharedVideoEditView/ViewModel/SharedVideoEditViewInput.swift, SharedVideoEditView/ViewModel/SharedVideoEditViewModel.swift, SharedVideoEditView/ViewModel/SharedVideoEditViewOutput.swift, - VideoListView/TempViewController.swift, VideoListView/VideoDetailView/VideoDetailViewController.swift, + VideoListView/VideoListContainer.swift, + VideoListView/VideoListCoordinator.swift, VideoListView/VideoListViewController.swift, VideoListView/View/CollectionView/VideoListCollectionViewCell.swift, VideoListView/View/CollectionView/VideoListDataSource.swift, diff --git a/Feature/Feature/ConnectionView/ConnectionCoordinator.swift b/Feature/Feature/ConnectionView/ConnectionCoordinator.swift new file mode 100644 index 00000000..ab18df9f --- /dev/null +++ b/Feature/Feature/ConnectionView/ConnectionCoordinator.swift @@ -0,0 +1,53 @@ +// +// ConnectionCoordinator.swift +// Feature +// +// Created by 이숲 on 12/5/24. +// + +import Core +import Interfaces +import UIKit + +public protocol ConnectionListener: AnyObject { } + +final class ConnectionCoordinator: Coordinator, ConnectionCoordinatable { + weak var listener: ConnectionListener? + private let viewModel: ConnectionViewModel + + private let videoListContainer: VideoListContainable + private var videoListCoordinator: Coordinatable? + + override func start(_ navigationController: UINavigationController?) { + super.start(navigationController) + + detachVideoList() + viewModel.fetchBrowedUsers() + } + + init(viewModel: ConnectionViewModel, videoListContainable: VideoListContainable) { + self.viewModel = viewModel + self.videoListContainer = videoListContainable + super.init(viewController: ConnectionViewController(viewModel: viewModel)) + viewModel.coordinator = self + } + + func attachVideoList() { + guard videoListCoordinator == nil else { return } + viewModel.isNotCurrentPresentedView() + let coordinator = videoListContainer.coordinator(listener: self) + addChild(coordinator) + coordinator.start(navigationController) + videoListCoordinator = coordinator + } + + func detachVideoList() { + guard let coordinator = videoListCoordinator else { return } + + removeChild(coordinator) + videoListCoordinator = nil + } +} + +// MARK: - +extension ConnectionCoordinator: VideoListListener { } diff --git a/Feature/Feature/ConnectionView/ConnectionViewContainer.swift b/Feature/Feature/ConnectionView/ConnectionViewContainer.swift new file mode 100644 index 00000000..ca8fa7c9 --- /dev/null +++ b/Feature/Feature/ConnectionView/ConnectionViewContainer.swift @@ -0,0 +1,33 @@ +// +// ConnectionViewContainer.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import Interfaces + +public protocol ConnectionDependency { + var videoListContaiable: VideoListContainable { get } + var browsingUseCase: BrowsingUserUseCaseInterface { get } +} + +public protocol ConnectionContainable { + func coordinator(listener: ConnectionListener) -> Coordinatable +} + +public final class ConnectionContainer: + Container, ConnectionContainable { + public func coordinator(listener: ConnectionListener) -> Coordinatable { + let viewModel = ConnectionViewModel(usecase: dependency.browsingUseCase) + + let coordinator = ConnectionCoordinator( + viewModel: viewModel, + videoListContainable: dependency.videoListContaiable + ) + coordinator.listener = listener + + return coordinator + } +} diff --git a/Feature/Feature/ConnectionView/ConnectionViewController.swift b/Feature/Feature/ConnectionView/ConnectionViewController.swift index 4bf4b775..ca286d12 100644 --- a/Feature/Feature/ConnectionView/ConnectionViewController.swift +++ b/Feature/Feature/ConnectionView/ConnectionViewController.swift @@ -102,8 +102,6 @@ extension ConnectionViewController { case .invitationTimeout: owner.resetCurrentAlert() owner.invitationTimeoutAlert() - case .openSharedVideoList: - owner.openVideoList() } } .store(in: &cancellables) @@ -187,14 +185,6 @@ private extension ConnectionViewController { currentUserId = nil } - func openVideoList() { - let videoListViewController = VideoListViewController( - viewModel: DIContainer.shared.resolve(type: MultipeerVideoListViewModel.self) - ) - guard navigationController?.viewControllers.last === self else { return } - self.navigationController?.pushViewController(videoListViewController, animated: true) - } - func presentInvitationReceivedAlert(by user: BrowsedUser) { let alert = UIAlertController( type: .invitationReceivedBy(name: user.name), diff --git a/Feature/Feature/ConnectionView/ViewModel/ConnectionViewModel.swift b/Feature/Feature/ConnectionView/ViewModel/ConnectionViewModel.swift index 5e27fce1..3faa9fc5 100644 --- a/Feature/Feature/ConnectionView/ViewModel/ConnectionViewModel.swift +++ b/Feature/Feature/ConnectionView/ViewModel/ConnectionViewModel.swift @@ -11,6 +11,10 @@ import Entity import Foundation import Interfaces +protocol ConnectionCoordinatable: AnyObject { + func attachVideoList() +} + final public class ConnectionViewModel { // MARK: - Typealias @@ -18,7 +22,7 @@ final public class ConnectionViewModel { typealias Output = ConnectionViewOutput // MARK: - Properties - + private var isCurrentPresentedView = true private let usecase: BrowsingUserUseCaseInterface private var output = PassthroughSubject() private var cancellables: Set = [] @@ -28,6 +32,8 @@ final public class ConnectionViewModel { private var outerRadius: CGFloat? private var usedPositions: [String: CGPoint] = [:] + weak var coordinator: ConnectionCoordinatable? + // MARK: - Initializer public init(usecase: BrowsingUserUseCaseInterface) { @@ -49,6 +55,16 @@ final public class ConnectionViewModel { guard self.centerPosition == currentCenter else { return false } return true } + + func fetchBrowedUsers() { + self.isCurrentPresentedView = true + usecase.startAdvertising() + usecase.fetchBrowsedUsers().forEach({ self.found(user: $0) }) + } + + func isNotCurrentPresentedView() { + self.isCurrentPresentedView = false + } } // MARK: - Transform @@ -61,7 +77,7 @@ extension ConnectionViewModel { switch result { // Connection Input case .fetchUsers: - usecase.fetchBrowsedUsers().forEach({ self.found(user: $0) }) + fetchBrowedUsers() case .inviteUser(let id): usecase.inviteUser(with: id) // Invitation Input @@ -81,11 +97,10 @@ extension ConnectionViewModel { } // MARK: - Binding - private extension ConnectionViewModel { func setupBind() { - // isInGroup usecase.isInGroup + .filter { [weak self] _ in self?.isCurrentPresentedView ?? false } .sink(with: self) { owner, value in value ? owner.usecase.stopAdvertising() : owner.usecase.startAdvertising() } @@ -140,9 +155,10 @@ private extension ConnectionViewModel { .store(in: &cancellables) usecase.openingEvent + .receive(on: DispatchQueue.main) .sink(with: self) { owner, _ in - owner.output.send(.openSharedVideoList) - owner.usecase.rejectInvitation() + owner.usecase.stopAdvertising() + owner.coordinator?.attachVideoList() } .store(in: &cancellables) } diff --git a/Feature/Feature/ConnectionView/ViewModel/ConnectionViewOutput.swift b/Feature/Feature/ConnectionView/ViewModel/ConnectionViewOutput.swift index 31fa3225..0b34cd65 100644 --- a/Feature/Feature/ConnectionView/ViewModel/ConnectionViewOutput.swift +++ b/Feature/Feature/ConnectionView/ViewModel/ConnectionViewOutput.swift @@ -23,5 +23,4 @@ enum ConnectionViewOutput { case connected(user: InvitedUser) case invitationRejectedBy(name: String) case invitationTimeout - case openSharedVideoList } diff --git a/Feature/Feature/GroupInfoView/GroupInfoContainer.swift b/Feature/Feature/GroupInfoView/GroupInfoContainer.swift new file mode 100644 index 00000000..b750710e --- /dev/null +++ b/Feature/Feature/GroupInfoView/GroupInfoContainer.swift @@ -0,0 +1,30 @@ +// +// GroupInfoContainer.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import Interfaces + +public protocol GroupInfoDependency { + var videoListContaiable: VideoListContainable { get } + var connectedUserUseCase: ConnectedUserUseCaseInterface { get } +} + +public protocol GroupInfoContainable { + func coordinator(listener: GroupInfoListener) -> Coordinatable +} + +public final class GroupInfoContainer: + Container, GroupInfoContainable { + public func coordinator(listener: GroupInfoListener) -> Coordinatable { + let viewModel = GroupInfoViewModel(usecase: dependency.connectedUserUseCase) + + let coordinator = GroupInfoCoordinator(viewModel: viewModel) + coordinator.listener = listener + + return coordinator + } +} diff --git a/Feature/Feature/GroupInfoView/GroupInfoCoordinator.swift b/Feature/Feature/GroupInfoView/GroupInfoCoordinator.swift new file mode 100644 index 00000000..3377a678 --- /dev/null +++ b/Feature/Feature/GroupInfoView/GroupInfoCoordinator.swift @@ -0,0 +1,29 @@ +// +// GroupInfoCoordinator.swift +// Feature +// +// Created by 이숲 on 12/5/24. +// + +import Core +import UIKit + +public protocol GroupInfoListener: AnyObject { + func exitGroupButtonDidTap() +} + +final class GroupInfoCoordinator: Coordinator, GroupInfoCoordinatable { + weak var listener: GroupInfoListener? + private let viewModel: GroupInfoViewModel + + init(viewModel: GroupInfoViewModel) { + self.viewModel = viewModel + let viewController = GroupInfoViewController(viewModel: viewModel) + super.init(viewController: viewController) + viewModel.coordinator = self + } + + func exitGroupButtonDidTap() { + listener?.exitGroupButtonDidTap() + } +} diff --git a/Feature/Feature/GroupInfoView/GroupInfoViewController.swift b/Feature/Feature/GroupInfoView/GroupInfoViewController.swift index 35942164..663b6d84 100644 --- a/Feature/Feature/GroupInfoView/GroupInfoViewController.swift +++ b/Feature/Feature/GroupInfoView/GroupInfoViewController.swift @@ -68,24 +68,22 @@ private extension GroupInfoViewController { view.addSubview(participantScrollView) view.addSubview(exitButton) -// exitButton.isEnabled = false participantScrollView.addSubview(participantStackView) } func setupViewConstraints() { countView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide).inset(10) + $0.top.equalToSuperview().offset(10) $0.trailing.equalToSuperview().inset(28) $0.height.equalTo(20) } participantScrollView.snp.makeConstraints { - $0.leading.equalToSuperview().inset(20) - $0.trailing.equalTo(exitButton.snp.leading).offset(-8) - $0.top.equalTo(view.safeAreaLayoutGuide).inset(30) + $0.leading.trailing.equalToSuperview().inset(20) + $0.top.equalTo(countView.snp.bottom).offset(20) $0.height.equalTo(35) } exitButton.snp.makeConstraints { - $0.top.equalTo(countView.snp.bottom).offset(10) + $0.top.equalTo(participantScrollView.snp.bottom).offset(10) $0.height.equalTo(38) $0.trailing.equalToSuperview().inset(14) } diff --git a/Feature/Feature/GroupInfoView/ViewModel/GroupInfoViewModel.swift b/Feature/Feature/GroupInfoView/ViewModel/GroupInfoViewModel.swift index 5e6e3aec..ec0f9d5a 100644 --- a/Feature/Feature/GroupInfoView/ViewModel/GroupInfoViewModel.swift +++ b/Feature/Feature/GroupInfoView/ViewModel/GroupInfoViewModel.swift @@ -9,6 +9,10 @@ import Combine import Entity import Interfaces +protocol GroupInfoCoordinatable: AnyObject { + func exitGroupButtonDidTap() +} + public final class GroupInfoViewModel { typealias Input = GroupInfoViewInput typealias Output = GroupInfoViewOutput @@ -19,6 +23,8 @@ public final class GroupInfoViewModel { var output = PassthroughSubject() var cancellables: Set = [] + weak var coordinator: GroupInfoCoordinatable? + public init(usecase: ConnectedUserUseCaseInterface) { self.usecase = usecase setupBind() @@ -85,5 +91,6 @@ private extension GroupInfoViewModel { func exitGroupButtonDidTab() { usecase.leaveGroup() + coordinator?.exitGroupButtonDidTap() } } diff --git a/Feature/Feature/MainViewController.swift b/Feature/Feature/MainViewController.swift deleted file mode 100644 index cb3829dc..00000000 --- a/Feature/Feature/MainViewController.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// MainViewController.swift -// Feature -// -// Created by 이숲 on 11/21/24. -// - -import SnapKit -import UIKit - -public final class MainViewController: UIViewController { - // MARK: - UI Components - - private let topViewController: UIViewController - private let bottomNavigationController: UINavigationController - - // MARK: - Initializer - - public init(topViewController: UIViewController, initialViewController: UIViewController) { - self.topViewController = topViewController - self.bottomNavigationController = UINavigationController(rootViewController: initialViewController) - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - LifeCycle - - public override func viewDidLoad() { - super.viewDidLoad() - - setupViewHierarchies() - setupViewConstraints() - } -} - -// MARK: - UI Configure - -private extension MainViewController { - enum Constants { - static let topViewHeight: CGFloat = 120 - } - - func setupViewHierarchies() { - [ - topViewController, - bottomNavigationController - ].forEach({ - addChild($0) - view.addSubview($0.view) - $0.didMove(toParent: self) - }) - } - - func setupViewConstraints() { - topViewController.view.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide) - make.leading.equalToSuperview() - make.trailing.equalToSuperview() - make.height.equalTo(Constants.topViewHeight) - } - - bottomNavigationController.view.snp.makeConstraints { make in - make.top.equalTo(topViewController.view.snp.bottom) - make.leading.equalToSuperview() - make.trailing.equalToSuperview() - make.bottom.equalTo(view.safeAreaLayoutGuide) - } - } -} diff --git a/Feature/Feature/PreviewView/PreviewContainer.swift b/Feature/Feature/PreviewView/PreviewContainer.swift new file mode 100644 index 00000000..1f3673e8 --- /dev/null +++ b/Feature/Feature/PreviewView/PreviewContainer.swift @@ -0,0 +1,30 @@ +// +// PreviewContainer.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import Interfaces + +public protocol PreviewDependency { + var editVideoUseCase: EditVideoUseCaseInterface { get } +} + +public protocol PreviewContainable { + func coordinator(listener: PreviewListener) -> Coordinatable +} + +public final class PreviewContainer: + Container, PreviewContainable { + public func coordinator(listener: PreviewListener) -> Coordinatable { + let viewModel = PreviewViewModel(usecase: dependency.editVideoUseCase) + + let coordinator = PreviewCoordinator(viewModel: viewModel) + + coordinator.listener = listener + + return coordinator + } +} diff --git a/Feature/Feature/PreviewView/PreviewCoordinator.swift b/Feature/Feature/PreviewView/PreviewCoordinator.swift new file mode 100644 index 00000000..2cd2294f --- /dev/null +++ b/Feature/Feature/PreviewView/PreviewCoordinator.swift @@ -0,0 +1,31 @@ +// +// PreviewCoordinator.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import UIKit + +public protocol PreviewListener: AnyObject { } + +final class PreviewCoordinator: Coordinator, PreviewCoordinatable { + weak var listener: PreviewListener? + + private let viewModel: PreviewViewModel + + init(viewModel: PreviewViewModel) { + self.viewModel = viewModel + let viewController = PreviewViewController(viewModel: viewModel) + super.init(viewController: viewController) + viewModel.coordinator = self + } + + override func start(_ navigationController: UINavigationController?) { + super.start(navigationController) + navigationController?.pushViewController(viewController, animated: true) + } + + func nextButtonDidTap() { } +} diff --git a/Feature/Feature/PreviewView/PreviewViewController.swift b/Feature/Feature/PreviewView/PreviewViewController.swift index 0b13d640..36791c54 100644 --- a/Feature/Feature/PreviewView/PreviewViewController.swift +++ b/Feature/Feature/PreviewView/PreviewViewController.swift @@ -6,8 +6,10 @@ // import AVFoundation +import Core import Combine import UIKit +import SnapKit public final class PreviewViewController: UIViewController { private let videoView = VideoPlayerView() @@ -100,12 +102,12 @@ private extension PreviewViewController { videoView.snp.makeConstraints { make in make.top.equalToSuperview() make.horizontalEdges.equalToSuperview() - make.height.equalTo(550) + make.height.equalTo(500) } saveButton.snp.makeConstraints { make in make.centerX.equalToSuperview() - make.top.equalTo(videoView.snp.bottom).offset(30) + make.top.equalTo(videoView.snp.bottom).offset(20) make.width.equalTo(160) make.height.equalTo(50) } diff --git a/Feature/Feature/PreviewView/ViewModel/PreviewViewModel.swift b/Feature/Feature/PreviewView/ViewModel/PreviewViewModel.swift index a4c4933c..04a16fa2 100644 --- a/Feature/Feature/PreviewView/ViewModel/PreviewViewModel.swift +++ b/Feature/Feature/PreviewView/ViewModel/PreviewViewModel.swift @@ -11,10 +11,13 @@ import Foundation import Photos import Interfaces +protocol PreviewCoordinatable: AnyObject { } + public final class PreviewViewModel { typealias Input = PreviewViewInput typealias Output = PreviewViewOutput + weak var coordinator: PreviewCoordinatable? var output = PassthroughSubject() var cancellables: Set = [] diff --git a/Feature/Feature/SharedVideoEditView/SharedVideoEditContainer.swift b/Feature/Feature/SharedVideoEditView/SharedVideoEditContainer.swift new file mode 100644 index 00000000..2ab9c6db --- /dev/null +++ b/Feature/Feature/SharedVideoEditView/SharedVideoEditContainer.swift @@ -0,0 +1,34 @@ +// +// SharedEditVideoContainer.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import Interfaces + +public protocol SharedEditVideoDependency { + var editVideoUseCase: EditVideoUseCaseInterface { get } + var previewContainer: PreviewContainable { get } +} + +public protocol SharedEditVideoContainable { + func coordinator(listener: SharedVideoEditListener) -> Coordinatable +} + +public final class SharedVideoEditContainer: + Container, SharedEditVideoContainable { + public func coordinator(listener: SharedVideoEditListener) -> Coordinatable { + let viewModel = SharedVideoEditViewModel(usecase: dependency.editVideoUseCase) + + let coordinator = SharedVideoEditCoordinator( + viewModel: viewModel, + previewContainer: dependency.previewContainer + ) + + coordinator.listener = listener + + return coordinator + } +} diff --git a/Feature/Feature/SharedVideoEditView/SharedVideoEditCoordinator.swift b/Feature/Feature/SharedVideoEditView/SharedVideoEditCoordinator.swift new file mode 100644 index 00000000..101f4664 --- /dev/null +++ b/Feature/Feature/SharedVideoEditView/SharedVideoEditCoordinator.swift @@ -0,0 +1,48 @@ +// +// SharedVideoEditCoordinator.swift +// Feature +// +// Created by 이숲 on 12/5/24. +// + +import Core +import UIKit + +public protocol SharedVideoEditListener: AnyObject { } + +final class SharedVideoEditCoordinator: Coordinator, SharedVideoEditCoordinatable { + weak var listener: SharedVideoEditListener? + + private let previewContainer: PreviewContainable + private var previewCoordinator: Coordinatable? + + private let viewModel: SharedVideoEditViewModel + + init( + viewModel: SharedVideoEditViewModel, + previewContainer: PreviewContainable + ) { + self.viewModel = viewModel + self.previewContainer = previewContainer + let viewController = SharedVideoEditViewController(viewModel: viewModel) + super.init(viewController: viewController) + viewModel.coordinator = self + } + + override func start(_ navigationController: UINavigationController?) { + super.start(navigationController) + navigationController?.pushViewController(viewController, animated: true) + } + + func attachPreview() { + guard previewCoordinator == nil else { return } + + let coordinator = previewContainer.coordinator(listener: self) + addChild(coordinator) + coordinator.start(navigationController) + previewCoordinator = coordinator + } +} + +// MARK: - PreviewListener +extension SharedVideoEditCoordinator: PreviewListener { } diff --git a/Feature/Feature/SharedVideoEditView/SharedVideoEditViewController.swift b/Feature/Feature/SharedVideoEditView/SharedVideoEditViewController.swift index d2d6541a..277f76d7 100644 --- a/Feature/Feature/SharedVideoEditView/SharedVideoEditViewController.swift +++ b/Feature/Feature/SharedVideoEditView/SharedVideoEditViewController.swift @@ -113,7 +113,7 @@ private extension SharedVideoEditViewController { nextButton.bs.tap .sink(with: self) { owner, _ in - owner.navigateToPreview() + owner.input.send(.nextButtonDidTap) } .store(in: &cancellables) } @@ -400,13 +400,6 @@ private extension SharedVideoEditViewController { .cancel()] ), animated: true) } - - func navigateToPreview() { - let previewViewController = PreviewViewController( - viewModel: DIContainer.shared.resolve(type: PreviewViewModel.self) - ) - navigationController?.pushViewController(previewViewController, animated: true) - } } // MARK: - UICollectionViewDelegate diff --git a/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewInput.swift b/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewInput.swift index 7ca1f148..1532229d 100644 --- a/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewInput.swift +++ b/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewInput.swift @@ -13,5 +13,6 @@ enum SharedVideoEditViewInput { case sliderModelLowerValueDidChanged(value: Double) case sliderModelUpperValueDidChanged(value: Double) case sliderEditSaveButtonDidTapped + case nextButtonDidTap case timelineCellOrderDidChanged(to: Int, url: URL) } diff --git a/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewModel.swift b/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewModel.swift index d539d49b..43f44545 100644 --- a/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewModel.swift +++ b/Feature/Feature/SharedVideoEditView/ViewModel/SharedVideoEditViewModel.swift @@ -11,6 +11,10 @@ import Core import Entity import Interfaces +protocol SharedVideoEditCoordinatable: AnyObject { + func attachPreview() +} + public final class SharedVideoEditViewModel { typealias Input = SharedVideoEditViewInput typealias Output = SharedVideoEditViewOutput @@ -22,6 +26,8 @@ public final class SharedVideoEditViewModel { private var tappedVideoPresentationModel: VideoPresentationModel? private let usecase: EditVideoUseCaseInterface + weak var coordinator: SharedVideoEditCoordinatable? + public init(usecase: EditVideoUseCaseInterface) { self.usecase = usecase self.setupBind() @@ -53,6 +59,8 @@ extension SharedVideoEditViewModel { startTime: currentTappedVideoPresentationModel.startTime, endTime: currentTappedVideoPresentationModel.endTime ) + case .nextButtonDidTap: + owner.coordinator?.attachPreview() case .timelineCellOrderDidChanged(let to, let url): owner.videoOrderChanged(to: to, url: url) } @@ -89,9 +97,6 @@ private extension SharedVideoEditViewModel { async let item = owner.makeVideoTimelineItem(with: video.url, asset: asset) await timeLineItem.append(item) } -// let newTimelineItems = await orderdVideos.asyncCompactMap { video in -// return await -// } owner.output.send(.timelineItemsDidChanged(items: timeLineItem)) } } diff --git a/Feature/Feature/VideoListView/TempViewController.swift b/Feature/Feature/VideoListView/TempViewController.swift deleted file mode 100644 index 17b1db27..00000000 --- a/Feature/Feature/VideoListView/TempViewController.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// TempViewController.swift -// Feature -// -// Created by 디해 on 11/22/24. -// - -import UIKit - -/// 편집 화면으로 이동하기 위한 임시 뷰 컨트롤러 입니다. -/// 추후 이 뷰 컨트롤러를 사용하여 편집 화면 뷰 컨트롤러를 구현해도 되고, 새로 구현하셔도 됩니다. -final class TempViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .blue - } -} diff --git a/Feature/Feature/VideoListView/VideoListContainer.swift b/Feature/Feature/VideoListView/VideoListContainer.swift new file mode 100644 index 00000000..7a1bbffc --- /dev/null +++ b/Feature/Feature/VideoListView/VideoListContainer.swift @@ -0,0 +1,33 @@ +// +// VideoListContainer.swift +// Feature +// +// Created by jung on 12/6/24. +// + +import Core +import Interfaces + +public protocol VideoListDependency { + var sharedEditVideoContainer: SharedEditVideoContainable { get } + var sharingVideoUseCase: SharingVideoUseCaseInterface { get } +} + +public protocol VideoListContainable { + func coordinator(listener: VideoListListener) -> Coordinatable +} + +public final class VideoListContainer: + Container, VideoListContainable { + public func coordinator(listener: VideoListListener) -> Coordinatable { + let viewModel = MultipeerVideoListViewModel(usecase: dependency.sharingVideoUseCase) + + let coordinator = VideoListCoordinator( + viewModel: viewModel, + sharedEditVideoContainer: dependency.sharedEditVideoContainer + ) + coordinator.listener = listener + + return coordinator + } +} diff --git a/Feature/Feature/VideoListView/VideoListCoordinator.swift b/Feature/Feature/VideoListView/VideoListCoordinator.swift new file mode 100644 index 00000000..fde621fd --- /dev/null +++ b/Feature/Feature/VideoListView/VideoListCoordinator.swift @@ -0,0 +1,48 @@ +// +// VideoListCoordinator.swift +// Feature +// +// Created by 이숲 on 12/5/24. +// + +import Core +import UIKit + +public protocol VideoListListener: AnyObject { } + +final class VideoListCoordinator: Coordinator, VideoListCoordinatable { + weak var listener: VideoListListener? + + private let viewModel: MultipeerVideoListViewModel + + private let sharedEditVideoContainer: SharedEditVideoContainable + private var sharedEditVideoCoordinator: Coordinatable? + + init( + viewModel: MultipeerVideoListViewModel, + sharedEditVideoContainer: SharedEditVideoContainable + ) { + self.viewModel = viewModel + self.sharedEditVideoContainer = sharedEditVideoContainer + let viewController = VideoListViewController(viewModel: viewModel) + super.init(viewController: viewController) + viewModel.coordinator = self + } + + override func start(_ navigationController: UINavigationController?) { + super.start(navigationController) + navigationController?.pushViewController(viewController, animated: true) + } + + func attachSharedEditVideo() { + guard sharedEditVideoCoordinator == nil else { return } + + let coordinator = sharedEditVideoContainer.coordinator(listener: self) + addChild(coordinator) + coordinator.start(navigationController) + sharedEditVideoCoordinator = coordinator + } +} + +// MARK: - SharedVideoEditListener +extension VideoListCoordinator: SharedVideoEditListener { } diff --git a/Feature/Feature/VideoListView/VideoListViewController.swift b/Feature/Feature/VideoListView/VideoListViewController.swift index 2e3fdeb6..9b9af43a 100644 --- a/Feature/Feature/VideoListView/VideoListViewController.swift +++ b/Feature/Feature/VideoListView/VideoListViewController.swift @@ -103,7 +103,6 @@ private extension VideoListViewController { self?.items = videos case .readyForNextScreen: self?.view.hideToast() - self?.navigateToEditor() case .startSynchronize: self?.pause() } @@ -193,13 +192,6 @@ private extension VideoListViewController { present(picker, animated: true, completion: nil) } - func navigateToEditor() { - let sharedVideoEditViewController = SharedVideoEditViewController( - viewModel: DIContainer.shared.resolve(type: SharedVideoEditViewModel.self) - ) - navigationController?.pushViewController(sharedVideoEditViewController, animated: true) - } - func pause() { view.showToast(message: "동기화 중입니다...") nextButton.isEnabled = false diff --git a/Feature/Feature/VideoListView/ViewModel/MockVideoListViewModel.swift b/Feature/Feature/VideoListView/ViewModel/MockVideoListViewModel.swift index 49ede225..db83d719 100644 --- a/Feature/Feature/VideoListView/ViewModel/MockVideoListViewModel.swift +++ b/Feature/Feature/VideoListView/ViewModel/MockVideoListViewModel.swift @@ -43,7 +43,7 @@ extension MockVideoListViewModel: VideoListViewModel { await self.appendItem(with: url) } case .validateSynchronization: - output.send(.readyForNextScreen) + break } } .store(in: &cancellables) diff --git a/Feature/Feature/VideoListView/ViewModel/MultipeerVideoListViewModel.swift b/Feature/Feature/VideoListView/ViewModel/MultipeerVideoListViewModel.swift index 3ed74665..594e1ab2 100644 --- a/Feature/Feature/VideoListView/ViewModel/MultipeerVideoListViewModel.swift +++ b/Feature/Feature/VideoListView/ViewModel/MultipeerVideoListViewModel.swift @@ -11,13 +11,19 @@ import Core import Entity import Interfaces +protocol VideoListCoordinatable: AnyObject { + func attachSharedEditVideo() +} + public final class MultipeerVideoListViewModel { private var videoItems: [VideoListItem] = [] private let usecase: SharingVideoUseCaseInterface var output = PassthroughSubject() var cancellables: Set = [] - + + weak var coordinator: VideoListCoordinatable? + public init(usecase: SharingVideoUseCaseInterface) { self.usecase = usecase setupBind() @@ -36,9 +42,10 @@ private extension MultipeerVideoListViewModel { .store(in: &cancellables) usecase.isSynchronized + .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self else { return } - output.send(.readyForNextScreen) + coordinator?.attachSharedEditVideo() } .store(in: &cancellables)