diff --git a/Projects/App/Sources/Splash/SplashViewController.swift b/Projects/App/Sources/Splash/SplashViewController.swift index 4ed4726e..7d93dd56 100644 --- a/Projects/App/Sources/Splash/SplashViewController.swift +++ b/Projects/App/Sources/Splash/SplashViewController.swift @@ -48,8 +48,6 @@ public final class SplashViewController: BaseViewController { extension SplashViewController { private func moveToMainView() { let fetchChannelListUsecase = DIContainer.shared.resolve(FetchChannelListUsecase.self) - let createChannelUsecase = DIContainer.shared.resolve(CreateChannelUsecase.self) - let deleteChannelUsecase = DIContainer.shared.resolve(DeleteChannelUsecase.self) let fetchChannelInfoUsecase = DIContainer.shared.resolve(FetchChannelInfoUsecase.self) let makeBroadcastUsecase = DIContainer.shared.resolve(MakeBroadcastUsecase.self) let fetchAllBroadcastUsecase = DIContainer.shared.resolve(FetchAllBroadcastUsecase.self) @@ -57,8 +55,6 @@ extension SplashViewController { let factory = DIContainer.shared.resolve(LiveStreamViewControllerFactory.self) let viewModel = BroadcastCollectionViewModel( fetchChannelListUsecase: fetchChannelListUsecase, - createChannelUsecase: createChannelUsecase, - deleteChannelUsecase: deleteChannelUsecase, fetchChannelInfoUsecase: fetchChannelInfoUsecase, makeBroadcastUsecase: makeBroadcastUsecase, fetchAllBroadcastUsecase: fetchAllBroadcastUsecase, @@ -66,10 +62,11 @@ extension SplashViewController { ) let viewController = BroadcastCollectionViewController(viewModel: viewModel, factory: factory) let navigationController = UINavigationController(rootViewController: viewController) - + let createChannelUsecase = DIContainer.shared.resolve(CreateChannelUsecase.self) + /// 유저의 이름이 저장되어 있지 않으면 유저 등록 뷰를 Navigation에 올림 if UserDefaults.standard.string(forKey: "USER_NAME") == nil { - let singUpViewModel = SignUpViewModel() + let singUpViewModel = SignUpViewModel(createChannelUsecase: createChannelUsecase) navigationController.viewControllers.append(SignUpViewController(viewModel: singUpViewModel)) } diff --git a/Projects/Features/AuthFeature/Demo/Sources/AppDelegate.swift b/Projects/Features/AuthFeature/Demo/Sources/AppDelegate.swift index a7d86acb..4e993edc 100644 --- a/Projects/Features/AuthFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Features/AuthFeature/Demo/Sources/AppDelegate.swift @@ -11,7 +11,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let viewModel = SignUpViewModel() + let mockCreateChannelUsecase = MockCreateChannelUsecaseImpl() + let viewModel = SignUpViewModel(createChannelUsecase: mockCreateChannelUsecase) let viewController = SignUpViewController(viewModel: viewModel) window?.rootViewController = viewController window?.makeKeyAndVisible() diff --git a/Projects/Features/AuthFeature/Demo/Sources/MockCreateChannelUsecaseImpl.swift b/Projects/Features/AuthFeature/Demo/Sources/MockCreateChannelUsecaseImpl.swift new file mode 100644 index 00000000..398358b4 --- /dev/null +++ b/Projects/Features/AuthFeature/Demo/Sources/MockCreateChannelUsecaseImpl.swift @@ -0,0 +1,11 @@ +import Combine + +import LiveStationDomainInterface + +final class MockCreateChannelUsecaseImpl: CreateChannelUsecase { + func execute(name: String) -> AnyPublisher { + Future { promise in + promise(.success(ChannelEntity(id: "", name: name))) + }.eraseToAnyPublisher() + } +} diff --git a/Projects/Features/AuthFeature/Sources/SignUpViewController.swift b/Projects/Features/AuthFeature/Sources/SignUpViewController.swift index 629b40f7..43e4ae17 100644 --- a/Projects/Features/AuthFeature/Sources/SignUpViewController.swift +++ b/Projects/Features/AuthFeature/Sources/SignUpViewController.swift @@ -25,7 +25,7 @@ public class SignUpViewController: BaseViewController { private let confettiAnimationView = LottieAnimationView(name: "confetti", bundle: Bundle(for: DesignSystemResources.self)) private let shookAnimationView = LottieAnimationView(name: "shook", bundle: Bundle(for: DesignSystemResources.self)) - private lazy var loadingView = SHLoadingView(message: "아이디 등록 중") + private lazy var loadingView = SHLoadingView(message: "닉네임 등록 중") public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -62,6 +62,7 @@ public class SignUpViewController: BaseViewController { .store(in: &cancellables) output.isSaved + .receive(on: DispatchQueue.main) .sink { [weak self] isSaved in if isSaved { self?.textField.resignFirstResponder() diff --git a/Projects/Features/AuthFeature/Sources/SignUpViewModel.swift b/Projects/Features/AuthFeature/Sources/SignUpViewModel.swift index 40a66db8..1097c5a3 100644 --- a/Projects/Features/AuthFeature/Sources/SignUpViewModel.swift +++ b/Projects/Features/AuthFeature/Sources/SignUpViewModel.swift @@ -2,6 +2,7 @@ import Combine import Foundation import BaseFeatureInterface +import LiveStationDomainInterface public class SignUpViewModel: ViewModel { public struct Input { @@ -16,6 +17,8 @@ public class SignUpViewModel: ViewModel { private let output = Output() private var cancellables = Set() + private let createChannelUsecase: any CreateChannelUsecase + public func transform(input: Input) -> Output { input.didWriteUserName .sink { [weak self] name in @@ -35,7 +38,9 @@ public class SignUpViewModel: ViewModel { return output } - public init() { } + public init(createChannelUsecase: CreateChannelUsecase) { + self.createChannelUsecase = createChannelUsecase + } private func validate(with name: String?) -> Bool { guard let name else { return false } @@ -47,6 +52,15 @@ public class SignUpViewModel: ViewModel { UserDefaults.standard.set(name, forKey: "USER_NAME") let savedName = UserDefaults.standard.string(forKey: "USER_NAME") - output.isSaved.send(savedName == name) + + createChannelUsecase.execute(name: "Guest") + .sink { _ in + } receiveValue: { [weak self] channelEntity in + UserDefaults.standard.set(channelEntity.id, forKey: "CHANNEL_ID") + let savedID = UserDefaults.standard.string(forKey: "CHANNEL_ID") + + self?.output.isSaved.send(savedName == name && savedID != nil) + } + .store(in: &cancellables) } } diff --git a/Projects/Features/MainFeature/Demo/Sources/AppDelegate.swift b/Projects/Features/MainFeature/Demo/Sources/AppDelegate.swift index 3c21585a..e2dab458 100644 --- a/Projects/Features/MainFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Features/MainFeature/Demo/Sources/AppDelegate.swift @@ -13,16 +13,12 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { ) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let mockFetchChannelListUsecase = MockFetchChannelListUsecaseImpl() - let mockCreateChannelUsecase = MockCreateChannelUsecaseImpl() - let mockDeleteChannelUsecase = MockDeleteChannelUsecaseImpl() let mockFetchChannelInfoUsecase = MockFetchChannelInfoUsecaseImpl() let mockmakeBroadcastUsecase = MockMakeBroadcastUsecaseImpl() let mockFetchAllBroadcastUsecase = MockFetchAllBroadcastUsecaseImpl() let mockDeleteBroadcastUsecase = MockDeleteBroadcastUsecaseImpl() let viewModel = BroadcastCollectionViewModel( fetchChannelListUsecase: mockFetchChannelListUsecase, - createChannelUsecase: mockCreateChannelUsecase, - deleteChannelUsecase: mockDeleteChannelUsecase, fetchChannelInfoUsecase: mockFetchChannelInfoUsecase, makeBroadcastUsecase: mockmakeBroadcastUsecase, fetchAllBroadcastUsecase: mockFetchAllBroadcastUsecase, @@ -37,5 +33,3 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return true } } - - diff --git a/Projects/Features/MainFeature/Sources/ViewControllers/BroadcastUIViewController.swift b/Projects/Features/MainFeature/Sources/ViewControllers/BroadcastUIViewController.swift index 6d661539..e68a5ef5 100644 --- a/Projects/Features/MainFeature/Sources/ViewControllers/BroadcastUIViewController.swift +++ b/Projects/Features/MainFeature/Sources/ViewControllers/BroadcastUIViewController.swift @@ -100,7 +100,8 @@ public final class BroadcastUIViewController: BaseViewController().eraseToAnyPublisher() } - return deleteChannelUsecase.execute(channelID: channel.id) - .zip(deleteBroadCastUsecase.execute(id: channel.id)) + guard let self, + let channelID else { return Empty().eraseToAnyPublisher() } + return deleteBroadCastUsecase.execute(id: channelID) .eraseToAnyPublisher() } .sink { _ in @@ -135,16 +129,12 @@ public class BroadcastCollectionViewModel: ViewModel { .store(in: &cancellables) input.didTapStartBroadcastButton - .flatMap { [weak self] _ in - guard let self else { return Empty().eraseToAnyPublisher() } - output.isReadyToStream.send(false) - return createChannelUsecase.execute(name: channelName) - } .flatMap { [weak self] in - guard let self else { return Empty().eraseToAnyPublisher() } - channel = $0 - return fetchChannelInfoUsecase.execute(channelID: $0.id) - .zip(makeBroadcastUsecase.execute(id: $0.id, title: $0.name, owner: userName, description: channelDescription)) + guard let self, + let channelID else { return Empty().eraseToAnyPublisher() } + output.isReadyToStream.send(false) + return fetchChannelInfoUsecase.execute(channelID: channelID) + .zip(makeBroadcastUsecase.execute(id: channelID, title: broadcastName, owner: userName, description: channelDescription)) .map { channelInfo, _ in channelInfo } .eraseToAnyPublisher() } @@ -168,7 +158,7 @@ public class BroadcastCollectionViewModel: ViewModel { let broadcast = broadcastInfoEntities.first { $0.id == channelEntity.id } return Channel( id: channelEntity.id, - title: channelEntity.name, + title: broadcast?.title ?? "Unknown", thumbnailImageURLString: channelEntity.imageURLString, owner: broadcast?.owner ?? "Unknown", description: broadcast?.description ?? "" @@ -210,15 +200,10 @@ public class BroadcastCollectionViewModel: ViewModel { /// - Parameter _: 방송 이름 /// - Returns: (Bool, String?) - 유효 여부와 에러 메시지 private func valid(_ value: String) -> (isValid: Bool, errorMessage: String?) { - let isLengthValid = 3...20 ~= value.count - let isCharactersValid = value.allSatisfy { $0.isLetter || $0.isNumber || $0 == "_" } + let trimmedValue = value.trimmingCharacters(in: .whitespaces) - if !isLengthValid && !isCharactersValid { - return (false, "3글자 이상,20글자 이하로 입력해 주세요. 특수문자는 언더바(_)만 가능합니다.") - } else if !isLengthValid { - return (false, "최소 3글자 이상, 최대 20글자 이하로 입력해 주세요.") - } else if !isCharactersValid { - return (false, "특수문자는 언더바(_)만 가능합니다.") + if trimmedValue.isEmpty { + return (false, "공백을 제외하고 최소 1글자 이상 입력해주세요.") } else { return (true, nil) }