-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 홈 화면 기능 구현 #179
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
[Feat] 홈 화면 기능 구현 #179
Changes from all commits
b6ea155
9a52115
ecef3e7
2995f50
afe3470
e0f8cb2
ec3ceaa
57fb957
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // | ||
| // CreateBannedUseCase.swift | ||
| // Wable-iOS | ||
| // | ||
| // Created by YOUJIM on 5/3/25. | ||
| // | ||
|
|
||
|
|
||
| import Combine | ||
| import Foundation | ||
|
|
||
| final class CreateBannedUseCase { | ||
| private let repository: ReportRepository | ||
|
|
||
| init(repository: ReportRepository) { | ||
| self.repository = repository | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Extension | ||
|
|
||
| extension CreateBannedUseCase { | ||
| func execute(memberID: Int, triggerType: TriggerType.Ban, triggerID: Int) -> AnyPublisher<Void, WableError> { | ||
| return repository.createBan(memberID: memberID, triggerType: triggerType, triggerID: triggerID) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // | ||
| // CreateReportUseCase.swift | ||
| // Wable-iOS | ||
| // | ||
| // Created by YOUJIM on 5/3/25. | ||
| // | ||
|
|
||
|
|
||
| import Combine | ||
| import Foundation | ||
|
|
||
| final class CreateReportUseCase { | ||
| private let repository: ReportRepository | ||
|
|
||
| init(repository: ReportRepository) { | ||
| self.repository = repository | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Extension | ||
|
|
||
| extension CreateReportUseCase { | ||
| func execute(nickname: String, text: String) -> AnyPublisher<Void, WableError> { | ||
| return repository.createReport(nickname: nickname, text: text) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // | ||
| // FetchGhostUseCase.swift | ||
| // Wable-iOS | ||
| // | ||
| // Created by YOUJIM on 5/2/25. | ||
| // | ||
|
|
||
|
|
||
| import Combine | ||
| import Foundation | ||
|
|
||
| final class FetchGhostUseCase { | ||
| private let repository: GhostRepository | ||
|
|
||
| init(repository: GhostRepository) { | ||
| self.repository = repository | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Extension | ||
|
|
||
| extension FetchGhostUseCase { | ||
| func execute(type: PostType, targetID: Int, userID: Int) -> AnyPublisher<Void, WableError> { | ||
| return repository.postGhostReduction( | ||
| alarmTriggerType: type == .comment ? "commentGhost" : "contentGhost", | ||
| alarmTriggerID: targetID, | ||
| targetMemberID: userID, | ||
| reason: "" | ||
| ) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,15 +25,23 @@ final class HomeViewController: NavigationViewController { | |
|
|
||
| // MARK: - Property | ||
|
|
||
| private var dataSource: DataSource? | ||
| var shouldShowLoadingScreen: Bool = false | ||
|
|
||
| private let viewModel: HomeViewModel | ||
| private let willAppearSubject = PassthroughSubject<Void, Never>() | ||
| private let didRefreshSubject = PassthroughSubject<Void, Never>() | ||
| private let didSelectedSubject = PassthroughSubject<Int, Never>() | ||
| private let didHeartTappedSubject = PassthroughSubject<(Int, Bool), Never>() | ||
| private let didGhostTappedSubject = PassthroughSubject<(Int, Int), Never>() | ||
| private let didDeleteTappedSubject = PassthroughSubject<Int, Never>() | ||
| private let didBannedTappedSubject = PassthroughSubject<(Int, Int), Never>() | ||
| private let didReportTappedSubject = PassthroughSubject<(String, String), Never>() | ||
| private let willDisplayLastItemSubject = PassthroughSubject<Void, Never>() | ||
| private let cancelBag: CancelBag | ||
| var shouldShowLoadingScreen: Bool = false | ||
|
|
||
| private var activeUserID: Int? | ||
| private var isActiveUserAdmin: Bool? | ||
| private var dataSource: DataSource? | ||
|
|
||
| // MARK: - UIComponent | ||
|
|
||
|
|
@@ -78,7 +86,7 @@ final class HomeViewController: NavigationViewController { | |
|
|
||
| shouldShowLoadingScreen ? showLoadingScreen() : nil | ||
|
|
||
| setupView( ) | ||
| setupView() | ||
| setupConstraint() | ||
| setupDataSource() | ||
| setupAction() | ||
|
|
@@ -128,7 +136,6 @@ private extension HomeViewController { | |
| func setupView() { | ||
| navigationController?.navigationBar.isHidden = true | ||
|
|
||
|
|
||
| view.addSubviews( | ||
| collectionView, | ||
| plusButton, | ||
|
|
@@ -158,10 +165,116 @@ private extension HomeViewController { | |
| } | ||
|
|
||
| func setupDataSource() { | ||
| let homeCellRegistration = CellRegistration<ContentCollectionViewCell, Content> { cell, indexPath, itemID in | ||
| cell.configureCell(info: itemID.content.contentInfo, postType: .mine, likeButtonTapHandler: { | ||
| self.didHeartTappedSubject.send((itemID.content.id, cell.likeButton.isLiked)) | ||
| }) | ||
| let homeCellRegistration = CellRegistration<ContentCollectionViewCell, Content> { [weak self] cell, indexPath, itemID in | ||
| guard let self = self else { return } | ||
|
|
||
| cell.configureCell( | ||
| info: itemID.content.contentInfo, | ||
| postType: itemID.content.contentInfo.author.id == self.activeUserID ? .mine : .others, | ||
| cellType: .list, | ||
| likeButtonTapHandler: { | ||
| self.didHeartTappedSubject.send((itemID.content.id, cell.likeButton.isLiked)) | ||
| }, | ||
| settingButtonTapHandler: { | ||
| let viewController = WableBottomSheetController() | ||
|
|
||
| if self.activeUserID == itemID.content.contentInfo.author.id { | ||
| viewController.addActions(WableBottomSheetAction(title: "삭제하기", handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| let viewController = WableSheetViewController(title: "게시글을 삭제하시겠어요?", message: "게시글이 영구히 삭제됩니다.") | ||
| viewController.addActions( | ||
| WableSheetAction(title: "취소", style: .gray), | ||
| WableSheetAction( | ||
| title: "삭제하기", | ||
| style: .primary, | ||
| handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| self.didDeleteTappedSubject.send(itemID.content.id) | ||
| }) | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| self.present(viewController, animated: true) | ||
| }) | ||
| })) | ||
| } else if self.isActiveUserAdmin ?? false { | ||
| viewController.addActions(WableBottomSheetAction(title: "신고하기", handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| let viewController = WableSheetViewController(title: "신고하시겠어요?") | ||
| viewController.addActions( | ||
| WableSheetAction(title: "취소", style: .gray), | ||
| WableSheetAction( | ||
| title: "신고하기", | ||
| style: .primary, | ||
| handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| self.didReportTappedSubject.send((itemID.content.contentInfo.title, itemID.content.contentInfo.text)) | ||
| }) | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| self.present(viewController, animated: true) | ||
| }) | ||
| }), WableBottomSheetAction(title: "밴하기", handler: { | ||
| self.didBannedTappedSubject.send((itemID.content.contentInfo.author.id, itemID.content.id)) | ||
| }) | ||
| ) | ||
| } else { | ||
| viewController.addActions(WableBottomSheetAction(title: "신고하기", handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| let viewController = WableSheetViewController(title: "신고하시겠어요?") | ||
| viewController.addActions( | ||
| WableSheetAction(title: "취소", style: .gray), | ||
| WableSheetAction( | ||
| title: "신고하기", | ||
| style: .primary, | ||
| handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| self.didReportTappedSubject.send((itemID.content.contentInfo.title, itemID.content.contentInfo.text)) | ||
| }) | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| self.present(viewController, animated: true) | ||
| }) | ||
| })) | ||
| } | ||
|
|
||
| self.present(viewController, animated: true) | ||
| }, | ||
| profileImageViewTapHandler: { | ||
| // TODO: 프로필 구현되는 대로 추가적인 설정 필요 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainResolve the TODO comment for profile implementation. There's a TODO comment that should be addressed when the profile functionality is available. 🏁 Script executed: #!/bin/bash
# Check if profile functionality is implemented elsewhere in the codebase
# Look for ProfileViewController implementation
echo "Checking ProfileViewController implementation:"
rg -A 3 "class ProfileViewController" --type swiftLength of output: 393 Implement Profile Integration and Remove TODO ProfileViewController is already implemented ( • File: // TODO: 프로필 구현되는 대로 추가적인 설정 필요with something like: let profileVC = ProfileViewController()
navigationController?.pushViewController(profileVC, animated: true)🧰 Tools🪛 SwiftLint (0.57.0)[Warning] 267-267: TODOs should be resolved (프로필 구현되는 대로 추가적인 설정 필요) (todo) |
||
| let viewController = ProfileViewController() | ||
|
|
||
| self.navigationController?.pushViewController(viewController, animated: true) | ||
| }, | ||
| ghostButtonTapHandler: { | ||
| let viewController = WableSheetViewController(title: "와블의 온화한 문화를 해치는\n누군가를 발견하신 건가요?") | ||
| viewController.addActions( | ||
| WableSheetAction( | ||
| title: "고민할게요", | ||
| style: .gray, | ||
| handler: { | ||
| viewController.dismiss(animated: true) | ||
| } | ||
|
Comment on lines
+260
to
+262
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 🫢 제가 확인을 제대로 못했네요 반영해두었습니다 ~! |
||
| ), | ||
| WableSheetAction( | ||
| title: "네 맞아요", | ||
| style: .primary, | ||
| handler: { | ||
| viewController.dismiss(animated: true, completion: { | ||
| self.didGhostTappedSubject.send((itemID.content.id, itemID.content.contentInfo.author.id)) | ||
| }) | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| self.present(viewController, animated: true) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, item in | ||
|
|
@@ -199,11 +312,29 @@ private extension HomeViewController { | |
| viewDidRefresh: didRefreshSubject.eraseToAnyPublisher(), | ||
| didSelectedItem: didSelectedSubject.eraseToAnyPublisher(), | ||
| didHeartTappedItem: didHeartTappedSubject.eraseToAnyPublisher(), | ||
| didGhostTappedItem: didGhostTappedSubject.eraseToAnyPublisher(), | ||
| didDeleteTappedItem: didDeleteTappedSubject.eraseToAnyPublisher(), | ||
| didBannedTappedItem: didBannedTappedSubject.eraseToAnyPublisher(), | ||
| didReportTappedItem: didReportTappedSubject.eraseToAnyPublisher(), | ||
| willDisplayLastItem: willDisplayLastItemSubject.eraseToAnyPublisher() | ||
| ) | ||
|
|
||
| let output = viewModel.transform(input: input, cancelBag: cancelBag) | ||
|
|
||
| output.isAdmin | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] isAdmin in | ||
| self?.isActiveUserAdmin = isAdmin | ||
| } | ||
| .store(in: cancelBag) | ||
|
|
||
| output.activeUserID | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] id in | ||
| self?.activeUserID = id | ||
| } | ||
| .store(in: cancelBag) | ||
|
Comment on lines
+324
to
+336
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반응형으로 동작해야 하지 않는 작업의 경우는, 단순하게 뷰모델의 프로퍼티로 관리해도 좋을 것 같아요.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이 부분 고민을 좀 했었는데요...! isAdmin과 activeUserID가 초기 로딩만 하고 거의 변하지 않는 값이라는 점에서 저도 굳이 이렇게 관리해야 하나? 라는 의문이 있었습니다. 그런데 모든 프로퍼티를 Input-Output으로 관리해야 하는줄 알고 있어서 ㅋㅋ ㅜㅜ 이렇게 구현했는데 프로퍼티로 빼도 되는 거였군요 ,,, 다만 리팩토링을 시도해봤는데 뷰모델에서 유저의 어드민 여부와 아이디를 받아온 후 해당 값을 뷰컨에서 받아 이후에 뷰들을 그려주는 과정이 필요한데 프로퍼티로 구현하게 되면 유저의 어드민 여부와 아이디를 받아오기 전에 뷰컨이 업데이트되면서 뷰가 제대로 그려지지 않는 오류가 있었습니다. 이 부분은 일단 머지해둘테니 몸 나아지시면 다음에 한번 같이 봐주시면 좋을 것 ,,, 같습니닷 ,,, ㅜㅜ |
||
|
|
||
| output.contents | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] contents in | ||
|
|
@@ -250,6 +381,18 @@ private extension HomeViewController { | |
| isLoadingMore ? self?.loadingIndicator.startAnimating() : self?.loadingIndicator.stopAnimating() | ||
| } | ||
| .store(in: cancelBag) | ||
|
|
||
| output.isReportSucceed | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { isSucceed in | ||
| let toast = ToastView( | ||
| status: .complete, | ||
| message: "신고 접수가 완료되었어요.\n24시간 이내에 조치할 예정이예요." | ||
| ) | ||
|
|
||
| isSucceed ? toast.show() : nil | ||
| } | ||
| .store(in: cancelBag) | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아무것도 넘겨주지 않아도 괜찮은 걸까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 세부 화면 쪽에서 구현된 부분이라 해당 PR에서는 반영하지 않았습니다 ~!!