Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] 화이트보드 입장 핸들링 #141

Merged
merged 7 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ import Foundation
public final class WhiteboardRepository: WhiteboardRepositoryInterface {
public weak var delegate: WhiteboardRepositoryDelegate?
public let recentPeerPublisher: AnyPublisher<Profile, Never>
public let connectionResultPublisher: AnyPublisher<Bool, Never>
private var nearbyNetwork: NearbyNetworkInterface
private var connections: [UUID: NetworkConnection]
private var participantsInfo: [String: String]
private let decoder = JSONDecoder()
private var myProfile: Profile
private var recentPeerSubject = PassthroughSubject<Profile, Never>()
private var connectionResultSubject = PassthroughSubject<Bool, Never>()

public init(nearbyNetworkInterface: NearbyNetworkInterface, myProfile: Profile) {
self.nearbyNetwork = nearbyNetworkInterface
self.participantsInfo = [:]
self.connections = [:]
self.myProfile = myProfile
self.recentPeerPublisher = recentPeerSubject.eraseToAnyPublisher()
self.connectionResultPublisher = connectionResultSubject.eraseToAnyPublisher()
self.nearbyNetwork.connectionDelegate = self
}

Expand Down Expand Up @@ -139,6 +142,7 @@ extension WhiteboardRepository: NearbyNetworkConnectionDelegate {
with context: Data?,
isHost: Bool
) {
connectionResultSubject.send(true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 얘가 보내는 값은 별로 중요하지 않은 건가요 ?? 항상 true를 보내고 UseCase에서 분기처리 ? ? ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뭔가 false를 보내줄 일이 있지 않을까.. 해서 남겨놓았습니다만..
조이가 보신대로 true를 보내주는 코드만 존재합니다.
변경하는게 맞을까요?
일단 true를 보내는 이유는 연결에 성공했기 때문입니다~!

do {
guard
isHost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
public protocol WhiteboardRepositoryInterface {
var delegate: WhiteboardRepositoryDelegate? { get set }
var recentPeerPublisher: AnyPublisher<Profile, Never> { get }
var connectionResultPublisher: AnyPublisher<Bool, Never> { get }

/// 주변에 내 기기를 참여자의 아이콘 정보와 함께 화이트보드를 알립니다.
/// - Parameter myProfile: 나의 프로필
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Combine

public protocol WhiteboardUseCaseInterface {
var whiteboardListPublisher: AnyPublisher<[Whiteboard], Never> { get }
var whiteboardConnectionPublisher: AnyPublisher<Bool, Never> { get }

/// 화이트보드를 생성합니다.
func createWhiteboard() -> Whiteboard
Expand Down
22 changes: 22 additions & 0 deletions Domain/Domain/Sources/UseCase/WhiteboardUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public final class WhiteboardUseCase: WhiteboardUseCaseInterface {
private var profileRepository: ProfileRepositoryInterface
private let whiteboardListSubject: CurrentValueSubject<[Whiteboard], Never>
public let whiteboardListPublisher: AnyPublisher<[Whiteboard], Never>
private let whiteboardConnectionSubject: PassthroughSubject<Bool, Never>
public let whiteboardConnectionPublisher: AnyPublisher<Bool, Never>
private var cancellables: Set<AnyCancellable>

public init(
whiteboardRepository: WhiteboardRepositoryInterface,
Expand All @@ -22,6 +25,9 @@ public final class WhiteboardUseCase: WhiteboardUseCaseInterface {
self.profileRepository = profileRepository
whiteboardListSubject = CurrentValueSubject<[Whiteboard], Never>([])
whiteboardListPublisher = whiteboardListSubject.eraseToAnyPublisher()
whiteboardConnectionSubject = PassthroughSubject<Bool, Never>()
whiteboardConnectionPublisher = whiteboardConnectionSubject.eraseToAnyPublisher()
cancellables = []
self.whiteboardRepository.delegate = self
}

Expand All @@ -48,12 +54,28 @@ public final class WhiteboardUseCase: WhiteboardUseCaseInterface {

public func joinWhiteboard(whiteboard: Whiteboard) throws {
let profile = profileRepository.loadProfile()
bindConnectionResult()
try whiteboardRepository.joinWhiteboard(whiteboard: whiteboard, myProfile: profile)
}

public func startSearchingWhiteboards() {
whiteboardRepository.startSearching()
}

private func bindConnectionResult() {
whiteboardRepository.connectionResultPublisher
.timeout(.seconds(3), scheduler: DispatchQueue.main)
.first()
.replaceEmpty(with: false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오우 이 오퍼레이터 동작 방식이 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • timeout: 3초동안만 이벤트를 수신합니다
  • first: 첫 번째 응답을 받고 구독을 취소합니다. (PR 내용의 일회성 구독을 위해 사용하였습니다)
  • replaceEmpty: 응답을 못받았다면 false를 방출합니다
    종합적으로 3초동안 이벤트를 받는데 다른 참가자가 연결된 경우에도 이벤트를 받지 않기 위해 일회성 구독을 하고, 아무 값도 받아오지 못한 경우 실패를 위해 빈 값을 fasle로 치환했습니다!!

.sink { [weak self] isConnected in
if isConnected {
self?.whiteboardConnectionSubject.send(true)
} else {
self?.whiteboardConnectionSubject.send(false)
}
}
.store(in: &cancellables)
}
}

extension WhiteboardUseCase: WhiteboardRepositoryDelegate {
Expand Down
4 changes: 4 additions & 0 deletions Domain/DomainTests/ManageWhiteboardObjectsUseCaseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,16 @@ final class MockWhiteObjectRepository: WhiteboardObjectRepositoryInterface {
}

final class MockWhiteboardRepository: WhiteboardRepositoryInterface {

var delegate: (any WhiteboardRepositoryDelegate)?
var recentPeerPublisher: AnyPublisher<Domain.Profile, Never>
var connectionResultPublisher: AnyPublisher<Bool, Never>
private var recentPeerSubject = PassthroughSubject<Domain.Profile, Never>()
private var connectionResultSubjuect = PassthroughSubject<Bool, Never>()

init() {
recentPeerPublisher = recentPeerSubject.eraseToAnyPublisher()
connectionResultPublisher = connectionResultSubjuect.eraseToAnyPublisher()
}

func startPublishing(myProfile: Profile) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public final class WhiteboardListViewController: UIViewController {
return label
}()

private var loadingIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .large)
indicator.hidesWhenStopped = true
return indicator
}()

private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
private let refreshControl = UIRefreshControl()
private var dataSource: UICollectionViewDiffableDataSource<Int, Whiteboard>?
Expand Down Expand Up @@ -217,6 +223,10 @@ public final class WhiteboardListViewController: UIViewController {
emptyListLabel
.addToSuperview(view)
.center(in: view)

loadingIndicator
.addToSuperview(view)
.center(in: view)
}

private func configureCollectionView() {
Expand Down Expand Up @@ -295,6 +305,36 @@ public final class WhiteboardListViewController: UIViewController {
dataSource.apply(snapshot, animatingDifferences: true)
}

private func moveToWhiteboard() {
let whiteboardViewModel = WhiteboardViewModel(
whiteboardUseCase: whiteboardUseCase,
photoUseCase: photoUseCase,
drawObjectUseCase: drawObjectUseCase,
textObjectUseCase: textObjectUseCase,
chatUseCase: chatUseCase,
gameObjectUseCase: gameObjectUseCase,
manageWhiteboardToolUseCase: manageWhiteboardToolUseCase,
manageWhiteboardObjectUseCase: manageWhiteboardObjectUseCase)
let whiteboardViewController = WhiteboardViewController(
viewModel: whiteboardViewModel,
objectViewFactory: whiteboardObjectViewFactory,
profileRepository: profileRepository,
chatUseCase: chatUseCase,
gameRepository: gameRepository)
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.pushViewController(whiteboardViewController, animated: true)
}

private func showFailAlert() {
let alertController = UIAlertController(
title: "연결에 실패했습니다",
message: "화이트보드에 연결할 수 없습니다. 다시 시도해주세요.",
preferredStyle: .alert)
let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}

private func bind() {
viewModel.output.whiteboardListPublisher
.receive(on: DispatchQueue.main)
Expand All @@ -303,6 +343,19 @@ public final class WhiteboardListViewController: UIViewController {
self?.applySnapshot(whiteboards: whiteboards)
}
.store(in: &cancellables)

viewModel.output.connectionStatusPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] isConnected in
self?.stopLoading()
if isConnected {
self?.moveToWhiteboard()
} else {
self?.showFailAlert()
self?.viewModel.action(input: .disconnectWhiteboard)
}
}
.store(in: &cancellables)
}

private func refreshWhiteboardList() {
Expand All @@ -311,29 +364,23 @@ public final class WhiteboardListViewController: UIViewController {
self?.refreshControl.endRefreshing()
}
}

private func startLoading() {
loadingIndicator.startAnimating()
view.isUserInteractionEnabled = false
}

private func stopLoading() {
loadingIndicator.stopAnimating()
view.isUserInteractionEnabled = true
}
}

// MARK: - UICollectionViewDelegate
extension WhiteboardListViewController: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedWhiteboard = dataSource?.itemIdentifier(for: indexPath) else { return }
viewModel.action(input: .joinWhiteboard(whiteboard: selectedWhiteboard))
let whiteboardViewModel = WhiteboardViewModel(
whiteboardUseCase: whiteboardUseCase,
photoUseCase: photoUseCase,
drawObjectUseCase: drawObjectUseCase,
textObjectUseCase: textObjectUseCase,
chatUseCase: chatUseCase,
gameObjectUseCase: gameObjectUseCase,
manageWhiteboardToolUseCase: manageWhiteboardToolUseCase,
manageWhiteboardObjectUseCase: manageWhiteboardObjectUseCase)
let whiteboardViewController = WhiteboardViewController(
viewModel: whiteboardViewModel,
objectViewFactory: whiteboardObjectViewFactory,
profileRepository: profileRepository,
chatUseCase: chatUseCase,
gameRepository: gameRepository)
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.pushViewController(whiteboardViewController, animated: true)
startLoading()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,22 @@ public final class WhiteboardListViewModel: ViewModel {

struct Output {
let whiteboardListPublisher: AnyPublisher<[Whiteboard], Never>
let connectionStatusPublisher: AnyPublisher<Bool, Never>
}

let output: Output
private let whiteboardSubject: PassthroughSubject<Whiteboard, Never>
private let connectionStatusSubject: PassthroughSubject<Bool, Never>

public init(whiteboardUseCase: WhiteboardUseCaseInterface) {
self.whiteboardUseCase = whiteboardUseCase
whiteboardSubject = PassthroughSubject<Whiteboard, Never>()
connectionStatusSubject = PassthroughSubject<Bool, Never>()
self.output = Output(
whiteboardListPublisher: whiteboardUseCase.whiteboardListPublisher)
whiteboardListPublisher: whiteboardUseCase.whiteboardListPublisher,
connectionStatusPublisher: connectionStatusSubject.eraseToAnyPublisher()
)
bindWhiteboardConnectionResult()
}

func action(input: Input) {
Expand Down Expand Up @@ -75,4 +81,13 @@ public final class WhiteboardListViewModel: ViewModel {
private func disconnectWhiteboard() {
whiteboardUseCase.disconnectWhiteboard()
}

private func bindWhiteboardConnectionResult() {
whiteboardUseCase.whiteboardConnectionPublisher
.sink { [weak self] isConnected in
self?.connectionStatusSubject.send(isConnected)
}
.store(in: &cancellables)
}

}