diff --git a/Tidify/Targets/Tidify/Config/Debug.xcconfig b/Tidify/Targets/Tidify/Config/Debug.xcconfig index 2edd1c7a..0555a692 100644 --- a/Tidify/Targets/Tidify/Config/Debug.xcconfig +++ b/Tidify/Targets/Tidify/Config/Debug.xcconfig @@ -10,5 +10,5 @@ // https://help.apple.com/xcode/#/dev745c5c974 KAKAO_NATIVE_APP_KEY = 2f2bda6aa215b8ede225cc7247fa120c -BASE_URL = 118.67.134.190:8080 +BASE_URL = tdf-lb-316240643.ap-northeast-2.elb.amazonaws.com:8080 USER_AGENT = Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 diff --git a/Tidify/Targets/Tidify/Config/Release.xcconfig b/Tidify/Targets/Tidify/Config/Release.xcconfig index 9a4e3a89..686a97fc 100644 --- a/Tidify/Targets/Tidify/Config/Release.xcconfig +++ b/Tidify/Targets/Tidify/Config/Release.xcconfig @@ -10,5 +10,5 @@ // https://help.apple.com/xcode/#/dev745c5c974 KAKAO_NATIVE_APP_KEY = 2f2bda6aa215b8ede225cc7247fa120c -BASE_URL = 118.67.134.190:8080 +BASE_URL = tdf-lb-316240643.ap-northeast-2.elb.amazonaws.com:8080 USER_AGENT = Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 diff --git a/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderDetailRepository.swift b/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderDetailRepository.swift index ef16b242..3c7c9fa6 100644 --- a/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderDetailRepository.swift +++ b/Tidify/Targets/TidifyData/Sources/Repositories/DefaultFolderDetailRepository.swift @@ -19,8 +19,8 @@ final class DefaultFolderDetailRepository: FolderDetailRepository { } // MARK: - Methods - func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse { - let response = try await networkProvider.request(endpoint: FolderEndpoint.fetchBookmarkListInFolder(id: id), type: BookmarkListResponse.self) + func fetchBookmarkListInFolder(id: Int, subscribe: Bool) async throws -> FetchBookmarkResponse { + let response = try await networkProvider.request(endpoint: FolderEndpoint.fetchBookmarkListInFolder(id: id, subscribe: subscribe), type: BookmarkListResponse.self) return FetchBookmarkResponse( bookmarks: response.toDomain(), @@ -28,4 +28,28 @@ final class DefaultFolderDetailRepository: FolderDetailRepository { isLastPage: response.bookmarkListDTO.isLastPage ) } + + func subscribeFolder(id: Int) async throws { + let response = try await networkProvider.request(endpoint: FolderEndpoint.subscribeFolder(id: id), type: APIResponse.self) + + if !response.isSuccess { + throw FolderSubscriptionError.failSubscribe + } + } + + func stopSubscription(id: Int) async throws { + let response = try await networkProvider.request(endpoint: FolderEndpoint.stopSubscription(id: id), type: APIResponse.self) + + if !response.isSuccess { + throw FolderSubscriptionError.failStopSubscription + } + } + + func stopSharingFolder(id: Int) async throws { + let response = try await networkProvider.request(endpoint: FolderEndpoint.stopSharingFolder(id: id), type: APIResponse.self) + + if !response.isSuccess { + throw FolderSubscriptionError.failStopSharing + } + } } diff --git a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/BookmarkEndpoint.swift b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/BookmarkEndpoint.swift index 25d52af9..cfd10849 100644 --- a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/BookmarkEndpoint.swift +++ b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/BookmarkEndpoint.swift @@ -18,7 +18,7 @@ enum BookmarkEndpoint: EndpointType { } extension BookmarkEndpoint { - var baseRouthPath: String { + var baseRoutePath: String { return "/app/bookmarks" } @@ -26,15 +26,15 @@ extension BookmarkEndpoint { switch self { case .fetchBoomarkList(let request, let category): if request.keyword.isNotNil { - return AppProperties.baseURL + baseRouthPath + "/search" + return AppProperties.baseURL + baseRoutePath + "/search" } - return AppProperties.baseURL + (category == .normal ? baseRouthPath : baseRouthPath + "/star") + return AppProperties.baseURL + (category == .normal ? baseRoutePath : baseRoutePath + "/star") case .createBookmark: - return AppProperties.baseURL + baseRouthPath + return AppProperties.baseURL + baseRoutePath case .deleteBookmark(let id), .updateBookmark(let id, _): - return AppProperties.baseURL + baseRouthPath + "/\(id)" + return AppProperties.baseURL + baseRoutePath + "/\(id)" case .favoriteBookmark(let id): - return AppProperties.baseURL + baseRouthPath + "/star/\(id)" + return AppProperties.baseURL + baseRoutePath + "/star/\(id)" } } diff --git a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/EndpointType.swift b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/EndpointType.swift index 2f0cba70..c4ee509d 100644 --- a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/EndpointType.swift +++ b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/EndpointType.swift @@ -10,7 +10,7 @@ import Foundation import TidifyCore protocol EndpointType { - var baseRouthPath: String { get } + var baseRoutePath: String { get } var fullPath: String { get } var method: HTTPMethod { get } var parameters: [String: String]? { get } diff --git a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/FolderEndpoint.swift b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/FolderEndpoint.swift index 929996f3..35a196d0 100644 --- a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/FolderEndpoint.swift +++ b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/FolderEndpoint.swift @@ -12,38 +12,51 @@ import TidifyDomain enum FolderEndpoint: EndpointType { case createFolder(request: FolderRequestDTO) case fetchFolderList(start: Int, count: Int, category: FolderCategory) - case fetchBookmarkListInFolder(id: Int) + case fetchBookmarkListInFolder(id: Int, subscribe: Bool) case updateFolder(id: Int, request: FolderRequestDTO) case deleteFolder(id: Int) + case subscribeFolder(id: Int) + case stopSubscription(id: Int) + case stopSharingFolder(id: Int) } extension FolderEndpoint { - var baseRouthPath: String { - return "/app/folders" + var baseRoutePath: String { + return AppProperties.baseURL + "/app/folders" } var fullPath: String { switch self { case .createFolder: - return AppProperties.baseURL + baseRouthPath + return baseRoutePath case .fetchFolderList(_, _, let category): - let path: String = AppProperties.baseURL + baseRouthPath + let path: String = baseRoutePath switch category { case .normal: return path case .subscribe: return path + "/subscribed" case .share: return path + "/subscribing" } - case .fetchBookmarkListInFolder(let id): - return AppProperties.baseURL + baseRouthPath + "/\(id)/bookmarks" + case .fetchBookmarkListInFolder(let id, let subscribe): + var finalPath = baseRoutePath + "/\(id)/bookmarks" + if subscribe { + finalPath += "/shared" + } + return finalPath case .deleteFolder(let id), .updateFolder(let id, _): - return AppProperties.baseURL + baseRouthPath + "/\(id)" + return baseRoutePath + "/\(id)" + case .subscribeFolder(let id): + return baseRoutePath + "/subscribed/\(id)" + case .stopSubscription(let id): + return baseRoutePath + "/un-subscribed/\(id)" + case .stopSharingFolder(let id): + return baseRoutePath + "/\(id)/share-suspending" } } var method: HTTPMethod { switch self { - case .createFolder: + case .createFolder, .subscribeFolder, .stopSubscription, .stopSharingFolder: return .post case .fetchFolderList, .fetchBookmarkListInFolder: return .get @@ -71,8 +84,7 @@ extension FolderEndpoint { "label": request.color ] - case .deleteFolder, .fetchBookmarkListInFolder: - return nil + default: return nil } } } diff --git a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/UserEndpoint.swift b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/UserEndpoint.swift index 9287e38f..dceb6bcc 100644 --- a/Tidify/Targets/TidifyData/Sources/Services/Endpoint/UserEndpoint.swift +++ b/Tidify/Targets/TidifyData/Sources/Services/Endpoint/UserEndpoint.swift @@ -20,16 +20,16 @@ enum UserEndpoint: EndpointType { } extension UserEndpoint { - var baseRouthPath: String { + var baseRoutePath: String { return "/oauth2" } var fullPath: String { switch self { case .signIn: - return AppProperties.baseURL + baseRouthPath + "/login" + return AppProperties.baseURL + baseRoutePath + "/login" case .signOut: - return AppProperties.baseURL + baseRouthPath + "/withdrawal" + return AppProperties.baseURL + baseRoutePath + "/withdrawal" } } diff --git a/Tidify/Targets/TidifyDomain/Sources/DomainAssembly.swift b/Tidify/Targets/TidifyDomain/Sources/DomainAssembly.swift index acce3271..9bc8d0d6 100644 --- a/Tidify/Targets/TidifyDomain/Sources/DomainAssembly.swift +++ b/Tidify/Targets/TidifyDomain/Sources/DomainAssembly.swift @@ -48,7 +48,8 @@ public struct DomainAssembly: Assemblable { container.register(type: FolderDetailUseCase.self) { container in return DefaultFolderDetailUseCase( folderDetailRepository: container.resolve(type: FolderDetailRepository.self)!, - bookmarkRepository: container.resolve(type: BookmarkRepository.self)! + bookmarkRepository: container.resolve(type: BookmarkRepository.self)!, + folderRepository: container.resolve(type: FolderRepository.self)! ) } } diff --git a/Tidify/Targets/TidifyDomain/Sources/Entities/Folder.swift b/Tidify/Targets/TidifyDomain/Sources/Entities/Folder.swift index 165fa2d5..58471b1b 100644 --- a/Tidify/Targets/TidifyDomain/Sources/Entities/Folder.swift +++ b/Tidify/Targets/TidifyDomain/Sources/Entities/Folder.swift @@ -22,6 +22,10 @@ public struct Folder: Equatable { self.color = color self.count = count } + + public static func ==(lhs: Folder, rhs: Folder) -> Bool { + lhs.id == rhs.id && lhs.title == rhs.title && lhs.color == rhs.color + } } public extension Folder { diff --git a/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderDetailRepository.swift b/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderDetailRepository.swift index 490be608..fe2b57cb 100644 --- a/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderDetailRepository.swift +++ b/Tidify/Targets/TidifyDomain/Sources/Interfaces/FolderDetailRepository.swift @@ -9,5 +9,14 @@ public protocol FolderDetailRepository: AnyObject { /// 특정 폴더 ID에 포함된 북마크 리스트를 반환합니다. - func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse + func fetchBookmarkListInFolder(id: Int, subscribe: Bool) async throws -> FetchBookmarkResponse + + /// 특정 폴더를 구독합니다. + func subscribeFolder(id: Int) async throws + + /// 특정 폴더의 구독을 취소합니다. + func stopSubscription(id: Int) async throws + + /// 공유하고 있는 폴더의 공유를 중단합니다. + func stopSharingFolder(id: Int) async throws } diff --git a/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderDetailUseCase.swift b/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderDetailUseCase.swift index 96a146e3..7c16d262 100644 --- a/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderDetailUseCase.swift +++ b/Tidify/Targets/TidifyDomain/Sources/UseCases/Folder/FolderDetailUseCase.swift @@ -6,10 +6,20 @@ // Copyright © 2023 Tidify. All rights reserved. // -public protocol FolderDetailUseCase: BookmarkListUseCase { +public protocol FolderDetailUseCase: BookmarkListUseCase & FetchFolderUseCase { var bookmarkRepository: BookmarkRepository { get } - func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse + func fetchBookmarkListInFolder(id: Int, subscribe: Bool) async throws -> FetchBookmarkResponse + func subscribeFolder(id: Int) async throws + func stopSubscription(id: Int) async throws + func stopSharingFolder(id: Int) async throws +} + +public enum FolderSubscriptionError: Error { + case failStopSharing + case failStopSubscription + case failSharing + case failSubscribe } final class DefaultFolderDetailUseCase: FolderDetailUseCase { @@ -17,15 +27,33 @@ final class DefaultFolderDetailUseCase: FolderDetailUseCase { // MARK: - Properties private let folderDetailRepository: FolderDetailRepository let bookmarkRepository: BookmarkRepository + let folderRepository: FolderRepository // MARK: - Initializer - init(folderDetailRepository: FolderDetailRepository, bookmarkRepository: BookmarkRepository) { + init( + folderDetailRepository: FolderDetailRepository, + bookmarkRepository: BookmarkRepository, + folderRepository: FolderRepository + ) { self.folderDetailRepository = folderDetailRepository self.bookmarkRepository = bookmarkRepository + self.folderRepository = folderRepository } // MARK: - Methods - func fetchBookmarkListInFolder(id: Int) async throws -> FetchBookmarkResponse { - try await folderDetailRepository.fetchBookmarkListInFolder(id: id) + func fetchBookmarkListInFolder(id: Int, subscribe: Bool) async throws -> FetchBookmarkResponse { + try await folderDetailRepository.fetchBookmarkListInFolder(id: id, subscribe: subscribe) + } + + func subscribeFolder(id: Int) async throws { + try await folderDetailRepository.subscribeFolder(id: id) + } + + func stopSubscription(id: Int) async throws { + try await folderDetailRepository.stopSubscription(id: id) + } + + func stopSharingFolder(id: Int) async throws { + try await folderDetailRepository.stopSharingFolder(id: id) } } diff --git a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIColor+.swift b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIColor+.swift index f269017a..dee8e876 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIColor+.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIColor+.swift @@ -111,17 +111,17 @@ public extension UIColor { static func toColor(_ colorString: String) -> UIColor { switch colorString { - case LabelColors.ASHBLUE.colorString: return .t_ashBlue() + case LabelColors.ASHBLUE.colorString, "BLACK": return .t_ashBlue() case LabelColors.BLUE.colorString: return .t_blue() case LabelColors.PURPLE.colorString: return .t_purple() case LabelColors.GREEN.colorString: return .t_green() case LabelColors.YELLOW.colorString: return .t_yellow() case LabelColors.ORANGE.colorString: return .t_orange() case LabelColors.RED.colorString: return .t_red() - case LabelColors.MINT.colorString: return .t_mint() + case LabelColors.MINT.colorString, "SKYBLUE": return .t_mint() case LabelColors.INDIGO.colorString: return t_indigo() case LabelColors.PINK.colorString: return .t_pink() - default: return .init() + default: return .t_ashBlue() } } diff --git a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIView+.swift b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIView+.swift index 93b1b333..60c3b91f 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIView+.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Extension/UIView+.swift @@ -100,6 +100,7 @@ fileprivate extension UIView { override init(target: Any?, action: Selector?) { super.init(target: target, action: action) addTarget(self, action: #selector(tap)) + cancelsTouchesInView = false } @objc private func tap(sender: UITapGestureRecognizer) { diff --git a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/Alertable.swift b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/Alertable.swift index 02d2f031..4526584d 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/Alertable.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Common/Base/Protocol/Alertable.swift @@ -21,6 +21,11 @@ internal enum AlertType: CaseIterable { case folderFetchError case bookmarkCreationError case bookmarkFetchError + case bookmarkFavoriteError + case bookmarkDeleteError + case stopFolderSharingError + case stopFolderSubscriptionError + case subscribeFolderError var title: String { switch self { @@ -35,6 +40,11 @@ internal enum AlertType: CaseIterable { case .folderFetchError: return "폴더를 불러올 수 없습니다" case .bookmarkCreationError: return "저장에 실패했습니다" case .bookmarkFetchError: return "북마크를 불러올 수 없습니다" + case .bookmarkDeleteError: return "북마크를 삭제할 수 없습니다" + case .bookmarkFavoriteError: return "북마크 즐겨찾기를 할 수 없습니다" + case .stopFolderSharingError: return "공유 중단에 실패했습니다." + case .stopFolderSubscriptionError: return "구독 취소에 실패했습니다" + case .subscribeFolderError: return "구독에 실패했습니다" } } @@ -49,7 +59,9 @@ internal enum AlertType: CaseIterable { case .loginError: return "네트워크 연결상태 혹은 선택한 플랫폼을 확인해주세요" case .folderCreationError: return "네트워크 연결상태 혹은 폴더 제목을 확인해주세요" case .folderFetchError, .bookmarkFetchError: return "네트워크 연결상태를 확인 후 다시 접속해주세요" + case .bookmarkFavoriteError, .bookmarkDeleteError: return "네트워크 연결상태 혹은 북마크 상태를 확인해주세요" case .bookmarkCreationError: return "네트워크 연결상태 혹은 URL을 확인해주세요" + case .stopFolderSharingError, .stopFolderSubscriptionError, .subscribeFolderError: return "네트워크 연결상태 혹은 폴더 상태를 확인해주세요" } } diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderCoordinator.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderCoordinator.swift index 153d61f7..24063906 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderCoordinator.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderCoordinator.swift @@ -6,16 +6,13 @@ // Copyright © 2022 Tidify. All rights reserved. // +import Combine import TidifyCore import TidifyDomain import UIKit protocol FolderNavigationBarDelegate: AnyObject { - func didTapFolderButton() - - //TODO: - 추후 구현 -// func didTapSubscribeButton() -// func didTapShareButton() + func didTapCategoryButton(category: FolderCategory) } protocol FolderCoordinator: Coordinator { @@ -37,36 +34,9 @@ final class DefaultFolderCoordinator: FolderCoordinator { var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController - private lazy var folderButton: UIButton = { - let button: UIButton = .init() - button.setTitle("폴더", for: .normal) - button.setTitleColor(.t_ashBlue(weight: 800), for: .normal) - button.titleLabel?.font = .t_EB(22) - button.addTarget(self, action: #selector(didTapFolderButton), for: .touchUpInside) - return button - }() - - private lazy var subscribeButton: UIButton = { - let button: UIButton = .init() - button.setTitle("구독", for: .normal) - button.setTitleColor(.t_ashBlue(weight: 300), for: .normal) - button.titleLabel?.font = .t_EB(22) - - //TODO: - 추후 구현 -// button.addTarget(self, action: #selector(didTapSubscribeButton), for: .touchUpInside) - return button - }() - - private lazy var shareButton: UIButton = { - let button: UIButton = .init() - button.setTitle("공유중", for: .normal) - button.setTitleColor(.t_ashBlue(weight: 300), for: .normal) - button.titleLabel?.font = .t_EB(22) - - //TODO: - 추후 구현 -// button.addTarget(self, action: #selector(didTapShareButton), for: .touchUpInside) - return button - }() + private let folderButton: FolderCategoryButton = .init(category: .normal) + private let subscribeButton: FolderCategoryButton = .init(category: .subscribe) + private let shareButton: FolderCategoryButton = .init(category: .share) private let leftButtonStackView: UIStackView = { let stackView: UIStackView = .init() @@ -82,6 +52,8 @@ final class DefaultFolderCoordinator: FolderCoordinator { return button }() + private var cancellable: Set = [] + // MARK: - Initialize init(navigationController: UINavigationController) { self.navigationController = navigationController @@ -96,6 +68,22 @@ final class DefaultFolderCoordinator: FolderCoordinator { } leftButtonStackView.addArrangedSubview(folderButton) + leftButtonStackView.addArrangedSubview(subscribeButton) + leftButtonStackView.addArrangedSubview(shareButton) + + Publishers.Merge3( + folderButton.tapPublisherWithCategory, + subscribeButton.tapPublisherWithCategory, + shareButton.tapPublisherWithCategory + ) + .withUnretained(self) + .sink(receiveValue: { (owner, category) in + owner.folderButton.setTitleColor(.t_ashBlue(weight: category == .normal ? 800 : 300), for: .normal) + owner.subscribeButton.setTitleColor(.t_ashBlue(weight: category == .subscribe ? 800 : 300), for: .normal) + owner.shareButton.setTitleColor(.t_ashBlue(weight: category == .share ? 800 : 300), for: .normal) + owner.navigationBarDelegate?.didTapCategoryButton(category: category) + }) + .store(in: &cancellable) let navigationBar: TidifyNavigationBar = .init( leftButtonStackView: leftButtonStackView, @@ -127,6 +115,7 @@ final class DefaultFolderCoordinator: FolderCoordinator { let folderDetailCoordinator: DefaultFolderDetailCoordinator = .init( navigationController: navigationController ) + let folderDetailViewController = folderDetailCoordinator.startPush(folder: folder) folderDetailCoordinator.parentCoordinator = self addChild(folderDetailCoordinator) @@ -172,26 +161,3 @@ final class DefaultFolderCoordinator: FolderCoordinator { parentCoordinator?.removeChild(self) } } - -private extension DefaultFolderCoordinator { - @objc func didTapFolderButton() { - navigationBarDelegate?.didTapFolderButton() - folderButton.setTitleColor(.t_ashBlue(weight: 800), for: .normal) - subscribeButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) - shareButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) - } - - //TODO: - 추후 구현 -// @objc func didTapSubscribeButton() { -// navigationBarDelegate?.didTapSubscribeButton() -// folderButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) -// subscribeButton.setTitleColor(.t_ashBlue(weight: 800), for: .normal) -// shareButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) -// } -// @objc func didTapShareButton() { -// navigationBarDelegate?.didTapShareButton() -// folderButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) -// subscribeButton.setTitleColor(.t_ashBlue(weight: 300), for: .normal) -// shareButton.setTitleColor(.t_ashBlue(weight: 800), for: .normal) -// } -} diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderDetailCoordinator.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderDetailCoordinator.swift index 14ccdaa1..040e6e13 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderDetailCoordinator.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/FolderDetailCoordinator.swift @@ -16,6 +16,13 @@ protocol FolderDetailCoordinator: Coordinator { func startWebView(bookmark: Bookmark) } +enum FolderDetailViewMode { + case ownerNotSharing + case owner + case subscribeNotSubscribe + case subscriber +} + final class DefaultFolderDetailCoordinator: FolderDetailCoordinator { // MARK: - Properties @@ -37,7 +44,7 @@ final class DefaultFolderDetailCoordinator: FolderDetailCoordinator { } let viewModel: FolderDetailViewModel = .init(useCase: useCase) - let viewController: FolderDetailViewController = .init(viewModel: viewModel, folder: folder) + let viewController: FolderDetailViewController = .init(viewModel: viewModel, folderID: folder.id, title: folder.title) viewController.coordinator = self return viewController diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderCategoryButton.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderCategoryButton.swift new file mode 100644 index 00000000..f6d7678b --- /dev/null +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderCategoryButton.swift @@ -0,0 +1,46 @@ +// +// FolderCategoryButton.swift +// TidifyPresentation +// +// Created by 한상진 on 2024/02/21. +// Copyright © 2024 Tidify. All rights reserved. +// + +import Combine +import TidifyDomain +import UIKit + +final class FolderCategoryButton: UIButton { + + // MARK: Properties + private let category: FolderCategory + + private var title: String { + switch category { + case .normal: return "폴더" + case .subscribe: return "구독" + case .share: return "공유중" + } + } + + var tapPublisherWithCategory: AnyPublisher { + tapPublisher + .withUnretained(self) + .map { (owner, _) in owner.category } + .eraseToAnyPublisher() + } + + // MARK: Initializer + init(category: FolderCategory) { + self.category = category + super.init(frame: .zero) + + setTitleColor(.t_ashBlue(weight: category == .normal ? 800 : 300), for: .normal) + titleLabel?.font = .t_EB(22) + setTitle(title, for: .normal) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderDetailViewController.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderDetailViewController.swift index f3db1fdc..5edd6850 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderDetailViewController.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderDetailViewController.swift @@ -17,7 +17,10 @@ final class FolderDetailViewController: BaseViewController, Coordinatable, Alert // MARK: Properties weak var coordinator: DefaultFolderDetailCoordinator? private let viewModel: FolderDetailViewModel - private let folder: Folder + private let folderID: Int + private var isSubscribing: Bool { + viewModel.state.viewMode == .subscriber || viewModel.state.viewMode == .subscribeNotSubscribe + } var indicatorView: UIActivityIndicatorView = { let indicatorView: UIActivityIndicatorView = .init() @@ -46,6 +49,8 @@ final class FolderDetailViewController: BaseViewController, Coordinatable, Alert return contentView }() + private let shareButtonStackView: FolderShareButtonStackView = .init() + private lazy var tableView: UITableView = { let tableView: UITableView = .init(frame: .zero) tableView.t_registerCellClass(cellType: BookmarkCell.self) @@ -60,11 +65,11 @@ final class FolderDetailViewController: BaseViewController, Coordinatable, Alert }() // MARK: Initializer - init(viewModel: FolderDetailViewModel, folder: Folder) { + init(viewModel: FolderDetailViewModel, folderID: Int, title: String) { self.viewModel = viewModel - self.folder = folder + self.folderID = folderID super.init(nibName: nil, bundle: nil) - title = folder.title + self.title = title } required init?(coder: NSCoder) { @@ -72,10 +77,13 @@ final class FolderDetailViewController: BaseViewController, Coordinatable, Alert } override func viewDidLoad() { - viewModel.action(.initialize(folderID: folder.id)) + viewModel.action(.initialize(folderID: folderID, subscribe: isSubscribing)) + shareButtonStackView.setupStackView(viewMode: viewModel.state.viewMode) + super.viewDidLoad() setupLayoutConstraints() bindState() + shareButtonStackView.delegate = self } override func viewWillAppear(_ animated: Bool) { @@ -93,6 +101,7 @@ final class FolderDetailViewController: BaseViewController, Coordinatable, Alert view.addSubview(scrollView) scrollView.addSubview(contentView) + contentView.addSubview(shareButtonStackView) contentView.addSubview(tableView) view.addSubview(topEffectView) view.addSubview(indicatorView) @@ -124,9 +133,15 @@ private extension FolderDetailViewController { $0.height.equalToSuperview().priority(.low) } - tableView.snp.makeConstraints { + shareButtonStackView.snp.makeConstraints { $0.top.equalToSuperview().offset(Self.topPadding + navigationBarHeight + 15) $0.leading.trailing.equalToSuperview().inset(15) + $0.height.equalTo(45) + } + + tableView.snp.makeConstraints { + $0.top.equalTo(shareButtonStackView.snp.bottom).offset(15) + $0.leading.trailing.equalTo(shareButtonStackView) $0.height.equalTo(0) $0.bottom.equalToSuperview().offset(-60) } @@ -153,12 +168,38 @@ private extension FolderDetailViewController { .store(in: &cancellable) viewModel.$state - .map { $0.errorType } + .map { $0.bookmarkErrorType } .compactMap { $0 } - .filter { $0 == .failFetchBookmarks } .receiveOnMain() - .sink(receiveValue: { [weak self] _ in - self?.presentAlert(type: .bookmarkFetchError) + .sink(receiveValue: { [weak self] errorType in + switch errorType { + case .failFetchBookmarks: self?.presentAlert(type: .bookmarkFetchError) + case .failDeleteBookmark: self?.presentAlert(type: .bookmarkDeleteError) + case .failFavoriteBookmark: self?.presentAlert(type: .bookmarkFavoriteError) + default: return + } + }) + .store(in: &cancellable) + + viewModel.$state + .map { $0.folderSubscriptionErrorType } + .compactMap { $0 } + .receiveOnMain() + .sink(receiveValue: { [weak self] errorType in + switch errorType { + case .failStopSharing: self?.presentAlert(type: .stopFolderSharingError) + case .failStopSubscription: self?.presentAlert(type: .stopFolderSubscriptionError) + case .failSubscribe: self?.presentAlert(type: .subscribeFolderError) + default: return + } + }) + .store(in: &cancellable) + + viewModel.$state + .map { $0.viewMode } + .receiveOnMain() + .sink(receiveValue: { [weak self] viewMode in + self?.shareButtonStackView.setupStackView(viewMode: viewMode) }) .store(in: &cancellable) } @@ -188,7 +229,7 @@ extension FolderDetailViewController: UITableViewDataSource { } let cell: BookmarkCell = tableView.t_dequeueReusableCell(indexPath: indexPath) - cell.configure(bookmark: bookmark) + cell.configure(bookmark: bookmark, isSubscribing: isSubscribing) cell.delegate = self return cell @@ -213,6 +254,10 @@ extension FolderDetailViewController: UITableViewDelegate { _ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath ) -> UISwipeActionsConfiguration? { + guard viewModel.state.viewMode == .owner || viewModel.state.viewMode == .ownerNotSharing else { + return .none + } + guard let bookmark = viewModel.state.bookmarks[safe: indexPath.row] else { return .none } @@ -253,3 +298,15 @@ extension FolderDetailViewController: BookmarkCellDelegate { viewModel.action(.didTapStarButton(bookmarkID)) } } + +// MARK: - FolderShareButtonDelegate +extension FolderDetailViewController: FolderShareButtonDelegate { + func didTapLeftButton() { + viewModel.action(.didTapLeftButton(folderID)) + } + + func didTapRightButton() { + viewModel.action(.didTapRightButton(folderID)) + } +} + diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderShareButtonStackView.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderShareButtonStackView.swift new file mode 100644 index 00000000..567b0c3f --- /dev/null +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderShareButtonStackView.swift @@ -0,0 +1,103 @@ +// +// FolderShareButtonStackView.swift +// TidifyPresentation +// +// Created by 한상진 on 2024/01/22. +// Copyright © 2024 Tidify. All rights reserved. +// + +import Combine +import UIKit + +import SnapKit + +protocol FolderShareButtonDelegate: AnyObject { + func didTapLeftButton() + func didTapRightButton() +} + +final class FolderShareButtonStackView: UIStackView { + + // MARK: Properties + private var cancellable: Set = [] + weak var delegate: FolderShareButtonDelegate? + + let leftButton: UIButton = { + let button: UIButton = .init() + button.backgroundColor = .t_ashBlue(weight: 100) + button.setTitleColor(.t_ashBlue(), for: .normal) + button.titleLabel?.font = .t_B(15) + button.cornerRadius(radius: 15) + return button + }() + + let rightButton: UIButton = { + let button: UIButton = .init() + button.backgroundColor = .t_blue() + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = .t_B(15) + button.cornerRadius(radius: 15) + return button + }() + + // MARK: Initializer + init() { + super.init(frame: .zero) + + setupViews() + bindAction() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupStackView(viewMode: FolderDetailViewMode) { + switch viewMode { + case .ownerNotSharing: + rightButton.setTitle("공유하기", for: .normal) + leftButton.alpha = 0 + rightButton.alpha = 1 + case .owner: + leftButton.setTitle("공유 중단", for: .normal) + rightButton.setTitle("공유하기", for: .normal) + leftButton.alpha = 1 + rightButton.alpha = 1 + case .subscribeNotSubscribe: + rightButton.setTitle("구독하기", for: .normal) + leftButton.alpha = 0 + rightButton.alpha = 1 + case .subscriber: + leftButton.setTitle("구독 취소", for: .normal) + leftButton.alpha = 1 + rightButton.alpha = 0 + } + } +} + +// MARK: - Extension +private extension FolderShareButtonStackView { + func setupViews() { + spacing = 100 + distribution = .fillEqually + backgroundColor = .clear + addArrangedSubview(leftButton) + addArrangedSubview(rightButton) + } + + func bindAction() { + leftButton.tapPublisher + .withUnretained(self) + .sink(receiveValue: { owner, _ in + owner.delegate?.didTapLeftButton() + }) + .store(in: &cancellable) + + rightButton.tapPublisher + .withUnretained(self) + .sink(receiveValue: { owner, _ in + owner.delegate?.didTapRightButton() + }) + .store(in: &cancellable) + } +} diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderViewController.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderViewController.swift index 2140b710..cf228aa5 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderViewController.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/View/FolderViewController.swift @@ -208,16 +208,8 @@ private extension FolderViewController { // MARK: - Extension extension FolderViewController: FolderNavigationBarDelegate { - func didTapFolderButton() { - viewModel.action(.didTapCategory(.normal)) - } - - func didTapSubscribeButton() { - viewModel.action(.didTapCategory(.subscribe)) - } - - func didTapShareButton() { - viewModel.action(.didTapCategory(.share)) + func didTapCategoryButton(category: FolderCategory) { + viewModel.action(.didTapCategory(category)) } } @@ -269,6 +261,10 @@ extension FolderViewController: UITableViewDelegate { _ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath ) -> UISwipeActionsConfiguration? { + guard viewModel.state.category != .subscribe else { + return .none + } + guard let folder = viewModel.state.folders[safe: indexPath.row] else { return .none } diff --git a/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderDetailViewModel.swift b/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderDetailViewModel.swift index 1aaff202..c2f53472 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderDetailViewModel.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Folder/ViewModel/FolderDetailViewModel.swift @@ -13,15 +13,19 @@ final class FolderDetailViewModel: ViewModelType { typealias UseCase = FolderDetailUseCase enum Action { - case initialize(folderID: Int) + case initialize(folderID: Int, subscribe: Bool) case didTapDelete(_ bookmarkID: Int) case didTapStarButton(_ bookmarkID: Int) + case didTapLeftButton(_ folderID: Int) + case didTapRightButton(_ folderID: Int) } struct State: Equatable { var isLoading: Bool var bookmarks: [Bookmark] - var errorType: BookmarkListError? + var bookmarkErrorType: BookmarkListError? + var folderSubscriptionErrorType: FolderSubscriptionError? + var viewMode: FolderDetailViewMode } let useCase: UseCase @@ -29,33 +33,81 @@ final class FolderDetailViewModel: ViewModelType { init(useCase: UseCase) { self.useCase = useCase - state = .init(isLoading: false, bookmarks: []) + state = .init(isLoading: false, bookmarks: [], viewMode: .ownerNotSharing) } func action(_ action: Action) { - state.errorType = nil + state.bookmarkErrorType = nil + state.folderSubscriptionErrorType = nil switch action { - case .initialize(let folderID): - setupInitailBookmarks(folderID: folderID) + case .initialize(let folderID, let subscribe): + setupViewMode(folderID: folderID) + setupInitailBookmarks(folderID: folderID, subscribe: subscribe) case .didTapDelete(let bookmarkID): deleteBookmark(bookmarkID) case .didTapStarButton(let bookmarkID): didTapStarButton(bookmarkID) + case .didTapLeftButton(let folderID): + didTapLeftButton(folderID) + case .didTapRightButton(let folderID): + didTapRightButton(folderID) } } } private extension FolderDetailViewModel { - func setupInitailBookmarks(folderID: Int) { + func setupViewMode(folderID: Int) { Task { do { state.isLoading = true - let fetchBookmarkListResponse = try await useCase.fetchBookmarkListInFolder(id: folderID) + let fetchFolderListResponse = try await useCase.fetchFolderList(start: 0, count: 0, category: .normal) + + for folder in fetchFolderListResponse.folders where folder.id == folderID { + try await fetchSharingFolderList(folderID: folderID) + return + } + + try await fetchSubscribingFolderList(folderID: folderID) + state.isLoading = false + } catch { + state.bookmarkErrorType = .failFetchBookmarks + state.isLoading = false + } + } + } + + func fetchSharingFolderList(folderID: Int) async throws { + let fetchSharingFolderListResponse = try await useCase.fetchFolderList(start: 0, count: 0, category: .share) + + for folder in fetchSharingFolderListResponse.folders where folder.id == folderID { + state.viewMode = .owner + return + } + + state.viewMode = .ownerNotSharing + } + + func fetchSubscribingFolderList(folderID: Int) async throws { + let fetchSubscribingFolderListResponse = try await useCase.fetchFolderList(start: 0, count: 0, category: .subscribe) + + for folder in fetchSubscribingFolderListResponse.folders where folder.id == folderID { + state.viewMode = .subscriber + return + } + + state.viewMode = .subscribeNotSubscribe + } + + func setupInitailBookmarks(folderID: Int, subscribe: Bool) { + Task { + do { + state.isLoading = true + let fetchBookmarkListResponse = try await useCase.fetchBookmarkListInFolder(id: folderID, subscribe: subscribe) state.bookmarks = fetchBookmarkListResponse.bookmarks state.isLoading = false } catch { - state.errorType = .failFetchBookmarks + state.bookmarkErrorType = .failFetchBookmarks state.isLoading = false } } @@ -71,7 +123,7 @@ private extension FolderDetailViewModel { try await useCase.deleteBookmark(bookmarkID: bookmarkID) state.bookmarks.remove(at: index) } catch { - state.errorType = .failDeleteBookmark + state.bookmarkErrorType = .failDeleteBookmark } } } @@ -81,7 +133,64 @@ private extension FolderDetailViewModel { do { try await useCase.favoriteBookmark(id: bookmarkID) } catch { - state.errorType = .failFavoriteBookmark + state.bookmarkErrorType = .failFavoriteBookmark + } + } + } + + func didTapLeftButton(_ folderID: Int) { + Task { + do { + switch state.viewMode { + case .owner: + try await useCase.stopSharingFolder(id: folderID) + state.viewMode = .ownerNotSharing + + case .subscriber: + try await useCase.stopSubscription(id: folderID) + state.viewMode = .subscribeNotSubscribe + + default: return + } + } catch { + switch state.viewMode { + case .owner: + state.folderSubscriptionErrorType = .failStopSharing + + case .subscriber: + state.folderSubscriptionErrorType = .failStopSubscription + + default: return + } + } + } + } + + func didTapRightButton(_ folderID: Int) { + Task { + do { + switch state.viewMode { + case .owner, .ownerNotSharing: + //TODO: 구현 예정 + print("didTapShareButton") + + case .subscribeNotSubscribe: + try await useCase.subscribeFolder(id: folderID) + state.viewMode = .subscriber + + default: return + } + } catch { + switch state.viewMode { + case .ownerNotSharing: + //TODO: 구현 예정 + print("folderSharingError") + + case .subscribeNotSubscribe: + state.folderSubscriptionErrorType = .failSubscribe + + default: return + } } } } diff --git a/Tidify/Targets/TidifyPresentation/Sources/Home/View/BookmarkCell.swift b/Tidify/Targets/TidifyPresentation/Sources/Home/View/BookmarkCell.swift index 51f46959..eccfe5d6 100644 --- a/Tidify/Targets/TidifyPresentation/Sources/Home/View/BookmarkCell.swift +++ b/Tidify/Targets/TidifyPresentation/Sources/Home/View/BookmarkCell.swift @@ -60,9 +60,10 @@ final class BookmarkCell: UITableViewCell { } // MARK: - Methods - func configure(bookmark: Bookmark) { + func configure(bookmark: Bookmark, isSubscribing: Bool = false) { self.bookmark = bookmark updateUI(bookmark: bookmark) + starButton.isHidden = isSubscribing } }