diff --git a/SOPT-iOS/Projects/Core/Sources/Extension/UIKit+/UILabel+.swift b/SOPT-iOS/Projects/Core/Sources/Extension/UIKit+/UILabel+.swift index 5f342d27..646c2878 100644 --- a/SOPT-iOS/Projects/Core/Sources/Extension/UIKit+/UILabel+.swift +++ b/SOPT-iOS/Projects/Core/Sources/Extension/UIKit+/UILabel+.swift @@ -83,9 +83,10 @@ public extension UILabel { /// - defaultFont, defaultColor에는 기본 폰트와 컬러를 넣어주세요 func htmlToString(targetString: String, defaultFont: UIFont, - boldFont: UIFont, + boldFont: UIFont?, defaultColor: UIColor) { let text = targetString + let boldFont = boldFont ?? defaultFont guard let data = text.data(using: .utf8) else { return } do { diff --git a/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift b/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift index 9751f002..82405d69 100644 --- a/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift +++ b/SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift @@ -229,6 +229,12 @@ public struct I18N { public struct Home { public static let viewAll = "전체보기" + public struct PopUp { + public static let login = "로그인" + public static let needToLogin = "로그인이 필요해요" + public static let needToLoginDetail = "앱 서비스는 로그인을 해야만 사용할 수 있어요.\n솝트 회원이라면 로그인해주세요." + } + public struct DashBoard { public struct UserHistory { public static let visitor = "비회원" diff --git a/SOPT-iOS/Projects/Data/Sources/Repository/HomeRepository.swift b/SOPT-iOS/Projects/Data/Sources/Repository/HomeRepository.swift index 58bf1aef..7ea105ea 100644 --- a/SOPT-iOS/Projects/Data/Sources/Repository/HomeRepository.swift +++ b/SOPT-iOS/Projects/Data/Sources/Repository/HomeRepository.swift @@ -16,24 +16,51 @@ public class HomeRepository { private let homeService: HomeService private let calendarService: CalendarService + private let userService: UserService private let cancelBag = CancelBag() public init(homeService: HomeService, - calendarService: CalendarService + calendarService: CalendarService, + userService: UserService ) { self.homeService = homeService self.calendarService = calendarService + self.userService = userService } } extension HomeRepository: HomeRepositoryInterface { + public func getAppServices() -> AnyPublisher<[Domain.HomeAppServicesModel], any Error> { homeService.getAppServiceAccessStatus() .map { $0.map { $0.toDomain() } } .eraseToAnyPublisher() } + public func getUserInfo() -> AnyPublisher { + userService.getUserMainInfo() + .mapError { error -> MainError in + guard let error = error as? APIError else { + return MainError.networkError(message: error.localizedDescription) + } + + switch error { + case .network(let statusCode, _): + if statusCode == 401 { + return MainError.authFailed + } + return MainError.networkError(message: "\(statusCode) 네트워크 에러") + case .tokenReissuanceFailed: + return MainError.authFailed + default: + return MainError.networkError(message: error.localizedDescription) + } + } + .map { $0.toDomain() } + .eraseToAnyPublisher() + } + public func getInsightPosts() -> AnyPublisher<[Domain.HomeInsightPostsModel], any Error> { homeService.getInsightPosts() .map { $0.map { $0.toDomain() } } diff --git a/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift b/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift index 21324476..7ddc7f65 100644 --- a/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift +++ b/SOPT-iOS/Projects/Demo/Sources/Dependency/RegisterDependencies.swift @@ -175,7 +175,8 @@ extension AppDelegate { implement: { HomeRepository( homeService: DefaultHomeService(), - calendarService: DefaultCalendarService() + calendarService: DefaultCalendarService(), + userService: DefaultUserService() ) } ) diff --git a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/HomeRepositoryInterface.swift b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/HomeRepositoryInterface.swift index d94cc224..ac7b532a 100644 --- a/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/HomeRepositoryInterface.swift +++ b/SOPT-iOS/Projects/Domain/Sources/RepositoryInterface/HomeRepositoryInterface.swift @@ -12,6 +12,7 @@ import Core public protocol HomeRepositoryInterface { func getHomeDescription() -> AnyPublisher + func getUserInfo() -> AnyPublisher func getRecentSchedule() -> AnyPublisher func getAppServices() -> AnyPublisher<[HomeAppServicesModel], Error> func getInsightPosts() -> AnyPublisher<[HomeInsightPostsModel], Error> diff --git a/SOPT-iOS/Projects/Domain/Sources/UseCase/HomeUseCase.swift b/SOPT-iOS/Projects/Domain/Sources/UseCase/HomeUseCase.swift index b0536757..fa4cbbb4 100644 --- a/SOPT-iOS/Projects/Domain/Sources/UseCase/HomeUseCase.swift +++ b/SOPT-iOS/Projects/Domain/Sources/UseCase/HomeUseCase.swift @@ -12,6 +12,7 @@ import Core public protocol HomeUseCase { func getHomeDescription() -> AnyPublisher + func getUserInfo() -> AnyPublisher func getRecentSchedule() -> AnyPublisher func getAppServices() -> AnyPublisher<[HomeAppServicesModel], Never> func getInsightPosts() -> AnyPublisher<[HomeInsightPostsModel], Never> @@ -38,6 +39,13 @@ extension DefaultHomeUseCase: HomeUseCase { }.eraseToAnyPublisher() } + public func getUserInfo() -> AnyPublisher { + repository.getUserInfo() + .catch { error in + return Empty() + }.eraseToAnyPublisher() + } + public func getRecentSchedule() -> AnyPublisher { repository.getRecentSchedule() .catch { error in diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForMemberPresentable.swift b/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForMemberPresentable.swift index 3d55e753..8461d4e7 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForMemberPresentable.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForMemberPresentable.swift @@ -14,7 +14,12 @@ import Core public protocol HomeForMemberViewControllable: ViewControllable { } public protocol HomeForMemberCoordinatable { var onDashBoardCellTapped: (() -> Void)? { get set } - + var onCalendarCellTapped: (() -> Void)? { get set } + var onAttendanceButtonTapped: (() -> Void)? { get set } + var onMainProductCellTapped: ((String) -> Void)? { get set } + var onAppServiceCellTapped: ((String) -> Void)? { get set } + var onNotificationButtonTapped: (() -> Void)? { get set } + var onSettingButtonTapped: ((UserType) -> Void)? { get set } } public typealias HomeForMemberViewModelType = ViewModelType & HomeForMemberCoordinatable public typealias HomeForMemberPresentable = (vc: HomeForMemberViewControllable, vm: any HomeForMemberViewModelType) diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForVisitorPresentable.swift b/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForVisitorPresentable.swift index f07e4098..1ed06c91 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForVisitorPresentable.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Interface/Sources/HomeForVisitorPresentable.swift @@ -13,7 +13,9 @@ import Core public protocol HomeForVisitorViewControllable: ViewControllable { } public protocol HomeForVisitorCoordinatable { - + var onMainProductCellTapped: ((String) -> Void)? { get set } + var onAppServiceCellTapped: (() -> Void)? { get set } + var onSettingButtonTapped: ((UserType) -> Void)? { get set } } public typealias HomeForVisitorViewModelType = ViewModelType & HomeForVisitorCoordinatable public typealias HomeForVisitorPresentable = (vc: HomeForVisitorViewControllable, vm: any HomeForVisitorViewModelType) diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Announcements/AnnouncementCardCVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Announcements/AnnouncementCardCVC.swift index 062b0c88..952ed184 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Announcements/AnnouncementCardCVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Announcements/AnnouncementCardCVC.swift @@ -140,9 +140,7 @@ extension AnnouncementCardCVC { // MARK: - Methods extension AnnouncementCardCVC { - func configureCell(model: HomePresentationModel.Announcement?) { - guard let model else { return } - + func configureCell(model: HomePresentationModel.Announcement) { self.categoryTagView.setData(with: model.categoryName, isHotTag: false) self.categoryDetailLabel.text = "꿀팁" if let profileImage = model.profileImage { diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Calendar/CalendarCardCVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Calendar/CalendarCardCVC.swift index a28246a5..b46cfad5 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Calendar/CalendarCardCVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/Calendar/CalendarCardCVC.swift @@ -14,10 +14,13 @@ import DSKit final class CalendarCardCVC: UICollectionViewCell { + // MARK: - Properties + + private(set) lazy var attendanceButtonTap = attendanceButton.publisher(for: .touchUpInside) + // MARK: - UI Components private let dateLabel = UILabel().then { - $0.text = "10.22" $0.textColor = DSKitAsset.Colors.gray400.color $0.font = DSKitFontFamily.Suit.semiBold.font(size: 14) } @@ -25,7 +28,6 @@ final class CalendarCardCVC: UICollectionViewCell { private let scheduleCategoryTagView = HomeSquareTagView() private let scheduleTitleLabel = UILabel().then { - $0.text = "1차 행사" $0.textColor = DSKitAsset.Colors.white.color $0.font = DSKitFontFamily.Suit.semiBold.font(size: 16) } @@ -126,7 +128,8 @@ extension CalendarCardCVC { extension CalendarCardCVC { func configureCell(model: HomePresentationModel.RecentSchedule, userType: UserType) { - self.dateLabel.text = model.date + // TODO: 서버 - 날짜 포맷 변경 후 반영 + self.dateLabel.text = setDateFormat(date: model.date, to: "MM.dd") self.scheduleTitleLabel.text = model.title if let tagType = CalenderCategoryTagType(rawValue: model.type) { self.scheduleCategoryTagView.setData(title: tagType.text, diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/DashBoard/DashBoardCardCVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/DashBoard/DashBoardCardCVC.swift index 8fb671c8..5c5ed621 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/DashBoard/DashBoardCardCVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/DashBoard/DashBoardCardCVC.swift @@ -84,7 +84,7 @@ extension DashBoardCardCVC { // MARK: - Methods extension DashBoardCardCVC { - func configureCell(userType: UserType, description: String? = nil) { + func configureCell(userType: UserType, model: HomePresentationModel.DashBoard? = nil) { switch userType { case .visitor: self.descriptionLabel.font = DSKitFontFamily.Suit.medium.font(size: 18) @@ -92,14 +92,16 @@ extension DashBoardCardCVC { self.descriptionLabel.setLineSpacing(lineSpacing: 5) self.rightArrowWithCircleImageView.isHidden = true case .active, .inactive: - guard let description else { return } + guard let model else { return } + guard let description = model.description else { return } self.descriptionLabel.htmlToString(targetString: description, defaultFont: DSKitFontFamily.Suit.medium.font(size: 18), boldFont: DSKitFontFamily.Suit.bold.font(size: 18), defaultColor: DSKitAsset.Colors.white100.color) + self.descriptionLabel.setLineSpacing(lineSpacing: 5) self.rightArrowWithCircleImageView.isHidden = false + guard let history = model.history else { return } + userHistoryView.setData(userType: userType, recentHistory: 35, allHistory: history) } - - userHistoryView.setData(userType: userType, recentHistory: 35, allHistory: [35, 34, 33, 32, 31, 30, 29]) } } diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/SocialLinks/SocialLinkCardCVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/SocialLinks/SocialLinkCardCVC.swift index a18c3e4f..59bde381 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/SocialLinks/SocialLinkCardCVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Cells/SocialLinks/SocialLinkCardCVC.swift @@ -16,9 +16,7 @@ enum SocialLinkCardType: CaseIterable, Identifiable { case instagram case youtube - var id: UUID { - return UUID() - } + var id: Self { self } var image: UIImage { switch self { diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForMemberCompositionalLayout.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForMemberCompositionalLayout.swift index fb3f3e5c..99b3d62f 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForMemberCompositionalLayout.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForMemberCompositionalLayout.swift @@ -20,6 +20,7 @@ extension HomeForMemberVC { static let productItemSpacing: Double = 15 static let appServiceItemSpacing: Double = 16 + static let mainProductSectionSpacing: Double = 44 static let announcementWidth: Double = 300 } @@ -112,7 +113,7 @@ extension HomeForMemberVC { let section = NSCollectionLayoutSection(group: productGroup) section.contentInsets = NSDirectionalEdgeInsets(top: Metric.defaultItemSpacing, leading: Metric.collectionViewDefaultSideInset, - bottom: 40, + bottom: Metric.mainProductSectionSpacing, trailing: Metric.collectionViewDefaultSideInset) return section diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForVisitorCompositionalLayout.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForVisitorCompositionalLayout.swift index 49355069..5e4743e4 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForVisitorCompositionalLayout.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/CompositionalLayout/HomeForVisitorCompositionalLayout.swift @@ -19,6 +19,7 @@ extension HomeForVisitorVC { static let defaultSectionSpacing: Double = 36 static let productItemSpacing: Double = 15 + static let mainProductSectionSpacing: Double = 44 static let appServiceItemSpacing: Double = 16 } @@ -84,7 +85,7 @@ extension HomeForVisitorVC { section.boundarySupplementaryItems = [header] section.contentInsets = NSDirectionalEdgeInsets(top: Metric.defaultItemSpacing, leading: Metric.collectionViewDefaultSideInset, - bottom: 40, + bottom: Metric.mainProductSectionSpacing, trailing: Metric.collectionViewDefaultSideInset) return section diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForMemberItem.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForMemberItem.swift index 30b0dab2..e5b14f97 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForMemberItem.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForMemberItem.swift @@ -9,7 +9,7 @@ import Foundation enum HomeForMemberItem: Hashable { - case description(HomePresentationModel.Description) + case dashBoard(HomePresentationModel.DashBoard) case recentSchedule(HomePresentationModel.RecentSchedule) case productService(HomePresentationModel.ProductService) case appService(HomePresentationModel.AppService) diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForVisitorItem.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForVisitorItem.swift index 9c6a1485..11d9dcb0 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForVisitorItem.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Item/HomeForVisitorItem.swift @@ -9,7 +9,7 @@ import Foundation enum HomeForVisitorItem: Hashable { - case description(HomePresentationModel.Description) + case dashBoard(HomePresentationModel.DashBoard) case productService(HomePresentationModel.ProductService) case appService(HomePresentationModel.AppService) } diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForMemberItemProvider.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForMemberItemProvider.swift index ddb4f063..11c10994 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForMemberItemProvider.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForMemberItemProvider.swift @@ -8,66 +8,75 @@ import UIKit +import Combine + extension HomeForMemberVC { // cells - func createDashBoardCellRegistration() -> UICollectionView.CellRegistration { + func createDashBoardCellRegistration() -> DashBoardCardCellRegistration { collectionView.createCellRegistration { [weak self] cell, _, item in guard let self else { return } - cell.configureCell(userType: self.viewModel.userType, description: item.description) + cell.configureCell(userType: self.viewModel.userType, model: item) } } - func createCalendarCellRegistration() -> UICollectionView.CellRegistration { + func createCalendarCellRegistration() -> CalendarCellRegistration { collectionView.createCellRegistration { [weak self] cell, _, item in guard let self else { return } cell.configureCell(model: item, userType: self.viewModel.userType) + + cell.attendanceButtonTap + .withUnretained(self) + .sink { owner, _ in + owner.attendanceButtonTapped.send() + } + .store(in: cancelBag) } } - func createProductCellRegistration() -> UICollectionView.CellRegistration { + func createProductCellRegistration() -> ProductCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(title: item.name, image: item.image) } } - func createAppServiceCellRegistration() -> UICollectionView.CellRegistration { + func createAppServiceCellRegistration() -> AppServiceCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } - func createInsightCellRegistration() -> UICollectionView.CellRegistration { + func createInsightCellRegistration() -> InsightCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } - func createGroupCellRegistration() -> UICollectionView.CellRegistration { + func createGroupCellRegistration() -> GroupCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } - func createCoffeeChatCellRegistration() -> UICollectionView.CellRegistration { + func createCoffeeChatCellRegistration() -> CoffeeChatCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } - func createAnnouncementCellRegistration() -> UICollectionView.CellRegistration { + func createAnnouncementCellRegistration() -> AnnouncementCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } - func createSocialLinkCellRegistration() -> UICollectionView.CellRegistration { + func createSocialLinkCellRegistration() -> SocialLinkCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(type: item) } } // supplementary views - func createHeaderRegistration() -> UICollectionView.SupplementaryRegistration { + func createHeaderRegistration() -> HeaderRegistration { collectionView.createSupplementaryRegistration( elementKind: UICollectionView.elementKindSectionHeader ) { headerView, indexPath in @@ -76,7 +85,7 @@ extension HomeForMemberVC { } } - func createFooterRegistration() -> UICollectionView.SupplementaryRegistration { + func createFooterRegistration() -> FooterRegistration { collectionView.createSupplementaryRegistration( elementKind: UICollectionView.elementKindSectionFooter ) { [weak self] footerView, indexPath in diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForVisitorItemProvider.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForVisitorItemProvider.swift index a1974757..5cae6a2e 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForVisitorItemProvider.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeForVisitorItemProvider.swift @@ -10,27 +10,27 @@ import UIKit extension HomeForVisitorVC { // cells - func createDashBoardCellRegistration() -> UICollectionView.CellRegistration { + func createDashBoardCellRegistration() -> DashBoardCardCellRegistration { collectionView.createCellRegistration { [weak self] cell, _, _ in guard let self else { return } cell.configureCell(userType: self.viewModel.userType) } } - func createProductCellRegistration() -> UICollectionView.CellRegistration { + func createProductCellRegistration() -> ProductCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(title: item.name, image: item.image) } } - func createAppServiceCellRegistration() -> UICollectionView.CellRegistration { + func createAppServiceCellRegistration() -> AppServiceCellRegistration { collectionView.createCellRegistration { cell, _, item in cell.configureCell(model: item) } } // supplementary views - func createHeaderRegistration() -> UICollectionView.SupplementaryRegistration { + func createHeaderRegistration() -> HeaderRegistration { collectionView.createSupplementaryRegistration( elementKind: UICollectionView.elementKindSectionHeader ) { headerView, indexPath in diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeItemRegistrationTypes.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeItemRegistrationTypes.swift new file mode 100644 index 00000000..d42286cf --- /dev/null +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/CollectionView/Registration/HomeItemRegistrationTypes.swift @@ -0,0 +1,24 @@ +// +// HomeItemRegistrationTypes.swift +// HomeFeature +// +// Created by Jae Hyun Lee on 2/14/25. +// Copyright © 2025 SOPT-iOS. All rights reserved. +// + +import UIKit + +// cells +typealias DashBoardCardCellRegistration = UICollectionView.CellRegistration +typealias CalendarCellRegistration = UICollectionView.CellRegistration +typealias ProductCellRegistration = UICollectionView.CellRegistration +typealias AppServiceCellRegistration = UICollectionView.CellRegistration +typealias InsightCellRegistration = UICollectionView.CellRegistration +typealias GroupCellRegistration = UICollectionView.CellRegistration +typealias CoffeeChatCellRegistration = UICollectionView.CellRegistration +typealias AnnouncementCellRegistration = UICollectionView.CellRegistration +typealias SocialLinkCellRegistration = UICollectionView.CellRegistration + +// supplemenatry views +typealias HeaderRegistration = UICollectionView.SupplementaryRegistration +typealias FooterRegistration = UICollectionView.SupplementaryRegistration diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Coordinator/HomeCoordinator.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Coordinator/HomeCoordinator.swift index a73776c4..05c344b7 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Coordinator/HomeCoordinator.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Coordinator/HomeCoordinator.swift @@ -16,9 +16,27 @@ import BaseFeatureDependency import HomeFeatureInterface import WebFeature +public enum HomeCoordinatorDestination { + case signIn + case notification + case setting(userType: UserType) + case calendar + case attendance + case soptlog + + case webLink(url: String) + case deepLink(url: String) +} + +public protocol HomeCoordinatorOutput { + var requestCoordinating: ((HomeCoordinatorDestination) -> Void)? { get set } +} + +public typealias DefaultHomeCoordinator = BaseCoordinator & HomeCoordinatorOutput + public final class HomeCoordinator: DefaultCoordinator { - public var requestCoordinating: (() -> Void)? + public var requestCoordinating: ((HomeCoordinatorDestination) -> Void)? public var finishFlow: (() -> Void)? private let factory: HomeFeatureBuildable @@ -48,8 +66,31 @@ public final class HomeCoordinator: DefaultCoordinator { var homeForMember = factory.makeHomeForMember() homeForMember.vm.onDashBoardCellTapped = { [weak self] in - let homeCalendarDetail = self?.factory.makeHomeCalendarDetail() - self?.router.push(homeCalendarDetail?.vc) + self?.requestCoordinating?(.soptlog) + } + + homeForMember.vm.onCalendarCellTapped = { [weak self] in + self?.showHomeCalendarDetail() + } + + homeForMember.vm.onNotificationButtonTapped = { [weak self] in + self?.requestCoordinating?(.notification) + } + + homeForMember.vm.onSettingButtonTapped = { [weak self] userType in + self?.requestCoordinating?(.setting(userType: userType)) + } + + homeForMember.vm.onAppServiceCellTapped = { [weak self] url in + self?.requestCoordinating?(.deepLink(url: url)) + } + + homeForMember.vm.onMainProductCellTapped = { [weak self] url in + self?.requestCoordinating?(.webLink(url: url)) + } + + homeForMember.vm.onAttendanceButtonTapped = { [weak self] in + self?.requestCoordinating?(.attendance) } router.replaceRootWindow(homeForMember.vc, withAnimation: true) @@ -58,6 +99,26 @@ public final class HomeCoordinator: DefaultCoordinator { public func showHomeForVisitor() { var homeForVisitor = factory.makeHomeForVisitor() + homeForVisitor.vm.onAppServiceCellTapped = { + AlertUtils.presentAlertVC( + type: .titleDescription, + title: I18N.Home.PopUp.needToLogin, + description: I18N.Home.PopUp.needToLoginDetail, + customButtonTitle: I18N.Home.PopUp.login, + customAction: { [weak self] in + self?.requestCoordinating?(.signIn) + } + ) + } + + homeForVisitor.vm.onMainProductCellTapped = { [weak self] url in + self?.requestCoordinating?(.webLink(url: url)) + } + + homeForVisitor.vm.onSettingButtonTapped = { [weak self] userType in + self?.requestCoordinating?(.setting(userType: userType)) + } + router.replaceRootWindow(homeForVisitor.vc, withAnimation: true) } @@ -70,7 +131,7 @@ public final class HomeCoordinator: DefaultCoordinator { } homeCalendarDetail.vm.onAttendanceButtonTap = { [weak self] in - self?.requestCoordinating?() + self?.requestCoordinating?(.attendance) } self.router.push(homeCalendarDetail.vc) diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Models/HomePresentationModel.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Models/HomePresentationModel.swift index a8114079..8257af52 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Models/HomePresentationModel.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/Models/HomePresentationModel.swift @@ -13,23 +13,27 @@ import Domain struct HomePresentationModel { - let description: HomePresentationModel.Description + let dashBoard: HomePresentationModel.DashBoard let recentSchedule: HomePresentationModel.RecentSchedule let appServices: [HomePresentationModel.AppService] - let insightPosts: [HomePresentationModel.InsightPost] - let groupPosts: [HomePresentationModel.GroupPost] - let coffeeChatPosts: [HomePresentationModel.CoffeeChat] - let announcementPosts: [HomePresentationModel.Announcement] + + // TODO: 이후 스프린트에서 순차 배포 +// let insightPosts: [HomePresentationModel.InsightPost] +// let groupPosts: [HomePresentationModel.GroupPost] +// let coffeeChatPosts: [HomePresentationModel.CoffeeChat] +// let announcementPosts: [HomePresentationModel.Announcement] // MARK: - Item Structs - struct Description: Identifiable, Hashable { + struct DashBoard: Identifiable, Hashable { let id = UUID() - let description: String + let description: String? + let history: [Int]? - init(description: String) { + init(description: String? = nil, history: [Int]? = nil) { self.description = description + self.history = history } } @@ -52,16 +56,17 @@ struct HomePresentationModel { let name: String let image: UIImage + let url: String - init(name: String, image: UIImage) { + init(name: String, image: UIImage, url: String) { self.name = name self.image = image + self.url = url } } struct AppService: Identifiable, Hashable { - let id = UUID() - + var id: String let serviceName: String let displayAlarmBadge: Bool let alarmBadge, iconURL, deepLink: String @@ -78,6 +83,7 @@ struct HomePresentationModel { self.alarmBadge = alarmBadge self.iconURL = iconURL self.deepLink = deepLink + self.id = deepLink } } @@ -108,8 +114,7 @@ struct HomePresentationModel { } struct GroupPost: Identifiable, Hashable { - let id = UUID() - + let id: Int let title: String let category: HomeGroupPostModel.Category let canJoinOnlyActiveGeneration: Bool @@ -119,6 +124,7 @@ struct HomePresentationModel { let imageUrl: String init( + id: Int, title: String, category: HomeGroupPostModel.Category, canJoinOnlyActiveGeneration: Bool, @@ -127,6 +133,7 @@ struct HomePresentationModel { status: HomeGroupPostModel.Status, imageUrl: String ) { + self.id = id self.title = title self.category = category self.canJoinOnlyActiveGeneration = canJoinOnlyActiveGeneration @@ -138,8 +145,7 @@ struct HomePresentationModel { } struct CoffeeChat: Identifiable, Hashable { - let id = UUID() - + let id: Int let bio: String let topicTypeList: [String] let profileImage: String? @@ -151,6 +157,7 @@ struct HomePresentationModel { let currentSoptActivity: String? init( + id: Int, bio: String, topicTypeList: [String], profileImage: String? = nil, @@ -161,6 +168,7 @@ struct HomePresentationModel { soptActivities: [String], currentSoptActivity: String? = nil ) { + self.id = id self.bio = bio self.topicTypeList = topicTypeList self.profileImage = profileImage @@ -174,14 +182,14 @@ struct HomePresentationModel { } struct Announcement: Identifiable, Hashable { - let id = UUID() - + let id: Int let profileImage, name: String? let categoryName, title: String let content: String let images: [String]? init( + id: Int, profileImage: String? = nil, name: String? = nil, categoryName: String, @@ -189,6 +197,7 @@ struct HomePresentationModel { content: String, images: [String]? = nil ) { + self.id = id self.profileImage = profileImage self.name = name self.categoryName = categoryName @@ -202,9 +211,10 @@ struct HomePresentationModel { // MARK: - toPresentation extension HomeDescriptionModel { - func toPresentation() -> HomePresentationModel.Description { - return HomePresentationModel.Description( - description: self.description + func toPresentation(history: [Int]) -> HomePresentationModel.DashBoard { + return HomePresentationModel.DashBoard( + description: self.description, + history: history ) } } @@ -247,6 +257,7 @@ extension HomeInsightPostsModel { extension HomeGroupPostModel { func toPresentation() -> HomePresentationModel.GroupPost { return HomePresentationModel.GroupPost( + id: self.id, title: self.title, category: self.category, canJoinOnlyActiveGeneration: self.canJoinOnlyActiveGeneration, @@ -261,6 +272,7 @@ extension HomeGroupPostModel { extension HomeCoffeeChatPostModel { func toPresentation() -> HomePresentationModel.CoffeeChat { return HomePresentationModel.CoffeeChat( + id: self.memberId, bio: self.bio, topicTypeList: self.topicTypeList, name: self.name, @@ -272,6 +284,7 @@ extension HomeCoffeeChatPostModel { extension HomeAnnouncementModel { func toPresentation() -> HomePresentationModel.Announcement { return HomePresentationModel.Announcement( + id: self.id, profileImage: self.profileImage, name: self.name, categoryName: self.categoryName, diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForMemberVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForMemberVC.swift index c3a20c1e..7cd9af2c 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForMemberVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForMemberVC.swift @@ -20,8 +20,9 @@ final class HomeForMemberVC: UIViewController, HomeForMemberViewControllable { // MARK: - Properties public let viewModel: HomeForMemberViewModel - private var cancelBag = CancelBag() - private var cellTapped = PassthroughSubject() + private(set) var cancelBag = CancelBag() + private var cellTapped = PassthroughSubject() + private(set) var attendanceButtonTapped = PassthroughSubject() // MARK: - UI Components @@ -47,6 +48,7 @@ final class HomeForMemberVC: UIViewController, HomeForMemberViewControllable { configureHierarchy() configureUI() configureLayout() + configureDelegate() configureDataSource() bindViewModels() } @@ -76,7 +78,8 @@ extension HomeForMemberVC { ) naviBar.snp.makeConstraints { make in - make.leading.top.equalTo(view.safeAreaLayoutGuide) + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(40) } collectionView.snp.makeConstraints { make in @@ -89,23 +92,29 @@ extension HomeForMemberVC { // MARK: - Methods extension HomeForMemberVC { + private func configureDelegate() { + self.collectionView.delegate = self + } + private func configureDataSource() { let dashBoardRegistration = createDashBoardCellRegistration() let calendarRegistration = createCalendarCellRegistration() let mainProductRegistration = createProductCellRegistration() let appServiceRegistration = createAppServiceCellRegistration() - let insightRegistration = createInsightCellRegistration() - let groupRegistration = createGroupCellRegistration() - let coffeeChatRegistration = createCoffeeChatCellRegistration() - let announcementRegistration = createAnnouncementCellRegistration() - let socialLinkRegistration = createSocialLinkCellRegistration() + + // TODO: 이후 스프린트에서 순차 배포 +// let insightRegistration = createInsightCellRegistration() +// let groupRegistration = createGroupCellRegistration() +// let coffeeChatRegistration = createCoffeeChatCellRegistration() +// let announcementRegistration = createAnnouncementCellRegistration() +// let socialLinkRegistration = createSocialLinkCellRegistration() dataSource = UICollectionViewDiffableDataSource ( collectionView: collectionView) { (collectionView, indexPath, item) in switch item { - case .description(let description): + case .dashBoard(let dashBoard): return collectionView.dequeueConfiguredReusableCell(using: dashBoardRegistration, - for: indexPath, item: description) + for: indexPath, item: dashBoard) case .recentSchedule(let schedule): return collectionView.dequeueConfiguredReusableCell(using: calendarRegistration, for: indexPath, item: schedule) @@ -115,21 +124,23 @@ extension HomeForMemberVC { case .appService(let appService): return collectionView.dequeueConfiguredReusableCell(using: appServiceRegistration, for: indexPath, item: appService) - case .insightPost(let insight): - return collectionView.dequeueConfiguredReusableCell(using: insightRegistration, - for: indexPath, item: insight) - case .groupPost(let group): - return collectionView.dequeueConfiguredReusableCell(using: groupRegistration, - for: indexPath, item: group) - case .coffeeChat(let coffeeChat): - return collectionView.dequeueConfiguredReusableCell(using: coffeeChatRegistration, - for: indexPath, item: coffeeChat) - case .announcement(let announcement): - return collectionView.dequeueConfiguredReusableCell(using: announcementRegistration, - for: indexPath, item: announcement) - case .socialLink(let socialLink): - return collectionView.dequeueConfiguredReusableCell(using: socialLinkRegistration, - for: indexPath, item: socialLink) + // TODO: 이후 스프린트에서 순차 배포 + default: return UICollectionViewCell() +// case .insightPost(let insight): +// return collectionView.dequeueConfiguredReusableCell(using: insightRegistration, +// for: indexPath, item: insight) +// case .groupPost(let group): +// return collectionView.dequeueConfiguredReusableCell(using: groupRegistration, +// for: indexPath, item: group) +// case .coffeeChat(let coffeeChat): +// return collectionView.dequeueConfiguredReusableCell(using: coffeeChatRegistration, +// for: indexPath, item: coffeeChat) +// case .announcement(let announcement): +// return collectionView.dequeueConfiguredReusableCell(using: announcementRegistration, +// for: indexPath, item: announcement) +// case .socialLink(let socialLink): +// return collectionView.dequeueConfiguredReusableCell(using: socialLinkRegistration, +// for: indexPath, item: socialLink) } } @@ -138,25 +149,38 @@ extension HomeForMemberVC { private func configureSupplementaryView() { let headerRegistration = createHeaderRegistration() - let footerRegistration = createFooterRegistration() + // TODO: 이후 스프린트에서 순차 배포 +// let footerRegistration = createFooterRegistration() dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in if kind == UICollectionView.elementKindSectionHeader { return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath) } - if kind == UICollectionView.elementKindSectionFooter { - return collectionView.dequeueConfiguredReusableSupplementary(using: footerRegistration, for: indexPath) - } + // TODO: 이후 스프린트에서 순차 배포 +// if kind == UICollectionView.elementKindSectionFooter { +// return collectionView.dequeueConfiguredReusableSupplementary(using: footerRegistration, for: indexPath) +// } return UICollectionReusableView() } } private func bindViewModels() { + let noticeButtonTapped = naviBar.noticeButtonTap + .mapVoid() + .asDriver() + + let settingButtonTapped = naviBar.settingButtonTap + .mapVoid() + .asDriver() + let input = HomeForMemberViewModel.Input( + viewDidLoad: Just(()).asDriver(), cellTapped: cellTapped.asDriver(), - viewDidLoad: Just(()).asDriver() + attendanceButtonTapped: attendanceButtonTapped.asDriver(), + noticeButtonTapped: noticeButtonTapped, + settingButtonTapped: settingButtonTapped ) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) @@ -166,24 +190,53 @@ extension HomeForMemberVC { .sink { owner, data in owner.applySnapshot(with: data) }.store(in: cancelBag) + + output.isLoading + .withUnretained(self) + .sink { owner, isLoading in + isLoading ? owner.showLoading() : owner.stopLoading() + } + .store(in: cancelBag) } private func applySnapshot(with data: HomePresentationModel) { var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections(HomeForMemberSectionLayoutKind.allCases) + // TODO: 이후 스프린트에서 순차 배포 +// snapshot.appendSections(HomeForMemberSectionLayoutKind.allCases) + snapshot.appendSections([.dashBoard, .calendar, .mainProduct, .appService]) - snapshot.appendItems([.description(data.description)], toSection: .dashBoard) + snapshot.appendItems([.dashBoard(data.dashBoard)], toSection: .dashBoard) snapshot.appendItems([.recentSchedule(data.recentSchedule)], toSection: .calendar) snapshot.appendItems(self.viewModel.productServiceList.map { .productService($0) }, toSection: .mainProduct) snapshot.appendItems(data.appServices.map { .appService($0) }, toSection: .appService) - snapshot.appendItems([.insightPost(data.insightPosts.first!)], toSection: .insight) // 임시로 첫 번째 값만 배정 - snapshot.appendItems(data.groupPosts.map { .groupPost($0) }, toSection: .group) - snapshot.appendItems(data.coffeeChatPosts.map { .coffeeChat($0) }, toSection: .coffeeChat) - snapshot.appendItems(data.announcementPosts.map { .announcement($0) }, toSection: .announcement) - snapshot.appendItems(SocialLinkCardType.allCases.map { .socialLink($0) }, toSection: .socialLinks) - + // TODO: 이후 스프린트에서 순차 배포 +// snapshot.appendItems([.insightPost(data.insightPosts.first!)], toSection: .insight) // 임시로 첫 번째 값만 배정 +// snapshot.appendItems(data.groupPosts.map { .groupPost($0) }, toSection: .group) +// snapshot.appendItems(data.coffeeChatPosts.map { .coffeeChat($0) }, toSection: .coffeeChat) +// snapshot.appendItems(data.announcementPosts.map { .announcement($0) }, toSection: .announcement) +// snapshot.appendItems(SocialLinkCardType.allCases.map { .socialLink($0) }, toSection: .socialLinks) +// dataSource.apply(snapshot, animatingDifferences: true) } } +// MARK: - UICollectionViewDelegate + +extension HomeForMemberVC: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let selectedItem = dataSource.itemIdentifier(for: indexPath) { + switch selectedItem { + case .dashBoard(let model): + self.cellTapped.send(.dashBoard(model)) + case .recentSchedule(let model): + self.cellTapped.send(.recentSchedule(model)) + case .productService(let model): + self.cellTapped.send(.productService(model)) + case .appService(let model): + self.cellTapped.send(.appService(model)) + default: return + } + } + } +} diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForVisitorVC.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForVisitorVC.swift index 8ad05d25..e5a5bfda 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForVisitorVC.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/VC/HomeForVisitorVC.swift @@ -21,6 +21,7 @@ final class HomeForVisitorVC: UIViewController, HomeForVisitorViewControllable { public let viewModel: HomeForVisitorViewModel private var cancelBag = CancelBag() + private var cellTapped = PassthroughSubject() // MARK: - UI Components @@ -46,6 +47,7 @@ final class HomeForVisitorVC: UIViewController, HomeForVisitorViewControllable { configureHierarchy() configureUI() configureLayout() + configureDelegate() configureDataSource() bindViewModels() } @@ -75,7 +77,8 @@ extension HomeForVisitorVC { ) naviBar.snp.makeConstraints { make in - make.leading.top.equalTo(view.safeAreaLayoutGuide) + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(40) } collectionView.snp.makeConstraints { make in @@ -88,6 +91,10 @@ extension HomeForVisitorVC { // MARK: - Methods extension HomeForVisitorVC { + private func configureDelegate() { + self.collectionView.delegate = self + } + private func configureDataSource() { let dashBoardRegistration = createDashBoardCellRegistration() let mainProductRegistration = createProductCellRegistration() @@ -96,9 +103,9 @@ extension HomeForVisitorVC { dataSource = UICollectionViewDiffableDataSource ( collectionView: collectionView) { (collectionView, indexPath, item) in switch item { - case .description(let description): + case .dashBoard(let dashBoard): return collectionView.dequeueConfiguredReusableCell(using: dashBoardRegistration, - for: indexPath, item: description) + for: indexPath, item: dashBoard) case .productService(let productService): return collectionView.dequeueConfiguredReusableCell(using: mainProductRegistration, for: indexPath, item: productService) @@ -124,8 +131,14 @@ extension HomeForVisitorVC { } private func bindViewModels() { + let settingButtonTapped = naviBar.settingButtonTap + .mapVoid() + .asDriver() + let input = HomeForVisitorViewModel.Input( - viewDidLoad: Just(()).asDriver() + viewDidLoad: Just(()).asDriver(), + cellTapped: cellTapped.asDriver(), + settingButtonTapped: settingButtonTapped ) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) @@ -135,6 +148,12 @@ extension HomeForVisitorVC { .sink { owner, appService in owner.applySnapshot(with: appService) }.store(in: cancelBag) + + output.isLoading + .withUnretained(self) + .sink { owner, isLoading in + isLoading ? owner.showLoading() : owner.stopLoading() + }.store(in: cancelBag) } private func applySnapshot(with appService: [HomePresentationModel.AppService]) { @@ -142,10 +161,26 @@ extension HomeForVisitorVC { snapshot.appendSections(HomeForVisitorSectionLayoutKind.allCases) - snapshot.appendItems([.description(HomePresentationModel.Description(description: ""))], toSection: .dashBoard) + snapshot.appendItems([.dashBoard(HomePresentationModel.DashBoard())], toSection: .dashBoard) snapshot.appendItems(self.viewModel.productServiceList.map { .productService($0) }, toSection: .mainProduct) snapshot.appendItems(appService.map { .appService($0) }, toSection: .appService) dataSource.apply(snapshot, animatingDifferences: true) } } + +// MARK: - UICollectionViewDelegate + +extension HomeForVisitorVC: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let selectedItem = dataSource.itemIdentifier(for: indexPath) { + switch selectedItem { + case .productService(let model): + self.cellTapped.send(.productService(model)) + case .appService(let model): + self.cellTapped.send(.appService(model)) + default: return + } + } + } +} diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForMemberViewModel.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForMemberViewModel.swift index a6443a4d..a69c8e13 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForMemberViewModel.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForMemberViewModel.swift @@ -7,7 +7,6 @@ import Foundation -import UIKit import Combine import Core @@ -28,28 +27,38 @@ public class HomeForMemberViewModel: HomeForMemberViewModelType { let userType: UserType = UserDefaultKeyList.Auth.getUserType() let productServiceList: [HomePresentationModel.ProductService] = [ - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.playground, image: DSKitAsset.Assets.imgPlaygroundLogo.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.groupAndStudy, image: DSKitAsset.Assets.imgGroupLogo.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.member, image: DSKitAsset.Assets.imgMemberLogo.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.project, image: DSKitAsset.Assets.imgProjectLogo.image) + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.playground, image: DSKitAsset.Assets.imgPlaygroundLogo.image, url: ExternalURL.Playground.main), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.groupAndStudy, image: DSKitAsset.Assets.imgGroupLogo.image, url: ExternalURL.Playground.group), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.member, image: DSKitAsset.Assets.imgMemberLogo.image, url: ExternalURL.Playground.member), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.project, image: DSKitAsset.Assets.imgProjectLogo.image, url: ExternalURL.Playground.project) ] // MARK: - Inputs public struct Input { - let cellTapped: Driver let viewDidLoad: Driver + let cellTapped: Driver + let attendanceButtonTapped: Driver + let noticeButtonTapped: Driver + let settingButtonTapped: Driver } // MARK: - Outputs public struct Output { let homeItem = PassthroughSubject() + let isLoading = PassthroughSubject() } // MARK: - HomeForMemberCoordinating public var onDashBoardCellTapped: (() -> Void)? + public var onCalendarCellTapped: (() -> Void)? + public var onAttendanceButtonTapped: (() -> Void)? + public var onMainProductCellTapped: ((String) -> Void)? + public var onAppServiceCellTapped: ((String) -> Void)? + public var onNotificationButtonTapped: (() -> Void)? + public var onSettingButtonTapped: ((UserType) -> Void)? // MARK: - initialization @@ -63,47 +72,93 @@ extension HomeForMemberViewModel { let output = Output() input.viewDidLoad + .handleEvents(receiveOutput: { _ in + output.isLoading.send(true) + }) .flatMap { _ in + self.useCase.getUserInfo() + } + .compactMap { $0 } + .flatMap { userInfo in Publishers.Zip3( - self.useCase.getHomeDescription().map { $0.toPresentation() }, + self.useCase.getHomeDescription().map { $0.toPresentation(history: userInfo.historyList) }, self.useCase.getRecentSchedule().map { $0.toPresentation() }, self.useCase.getAppServices().map { $0.map { $0.toPresentation() } } ) - } - .flatMap { - description, - recentSchedule, - appService in - Publishers.Zip4( - self.useCase.getInsightPosts().map { $0.map { $0.toPresentation() } }, - self.useCase.getGroupPosts().map { $0.map { $0.toPresentation() } }, - self.useCase.getCoffeeChatPosts().map { $0.map { $0.toPresentation() } }, - self.useCase.getAnnouncementPosts().map { $0.map { $0.toPresentation() } } - ) - .map { insight, group, coffeeChat, announcement in + .map { dashBoard, recentSchedule, appService in HomePresentationModel( - description: description, + dashBoard: dashBoard, recentSchedule: recentSchedule, - appServices: appService, - insightPosts: insight, - groupPosts: group, - coffeeChatPosts: coffeeChat, - announcementPosts: announcement + appServices: appService ) } } + // TODO: 이후 스프린트에서 순차 배포 +// .flatMap { +// description, +// recentSchedule, +// appService in +// Publishers.Zip4( +// self.useCase.getInsightPosts().map { $0.map { $0.toPresentation() } }, +// self.useCase.getGroupPosts().map { $0.map { $0.toPresentation() } }, +// self.useCase.getCoffeeChatPosts().map { $0.map { $0.toPresentation() } }, +// self.useCase.getAnnouncementPosts().map { $0.map { $0.toPresentation() } } +// ) +// .map { insight, group, coffeeChat, announcement in +// HomePresentationModel( +// description: description, +// recentSchedule: recentSchedule, +// appServices: appService, +// insightPosts: insight, +// groupPosts: group, +// coffeeChatPosts: coffeeChat, +// announcementPosts: announcement +// ) +// } +// } .withUnretained(self) .sink { owner, data in output.homeItem.send(data) + output.isLoading.send(false) } .store(in: cancelBag) input.cellTapped - .filter{ $0.section == 1 } .withUnretained(self) - .sink(receiveValue: { owner, indexPath in - owner.onDashBoardCellTapped?() - }) + .sink { owner, item in + switch item { + case .dashBoard: + owner.onDashBoardCellTapped?() + case .recentSchedule: + owner.onCalendarCellTapped?() + case .productService(let model): + owner.onMainProductCellTapped?(model.url) + case .appService(let model): + owner.onAppServiceCellTapped?(model.deepLink) + default: break + } + } + .store(in: cancelBag) + + input.noticeButtonTapped + .withUnretained(self) + .sink { owner, _ in + owner.onNotificationButtonTapped?() + } + .store(in: cancelBag) + + input.settingButtonTapped + .withUnretained(self) + .sink { owner, _ in + owner.onSettingButtonTapped?(owner.userType) + } + .store(in: cancelBag) + + input.attendanceButtonTapped + .withUnretained(self) + .sink { owner, _ in + owner.onAttendanceButtonTapped?() + } .store(in: cancelBag) return output diff --git a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForVisitorViewModel.swift b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForVisitorViewModel.swift index f6517b72..25a44420 100644 --- a/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForVisitorViewModel.swift +++ b/SOPT-iOS/Projects/Features/HomeFeature/Sources/HomeScene/ViewModel/HomeForVisitorViewModel.swift @@ -20,32 +20,39 @@ public class HomeForVisitorViewModel: HomeForVisitorViewModelType { // MARK: - Properties - let userType: UserType = UserDefaultKeyList.Auth.getUserType() + private let useCase: HomeUseCase + private var cancelBag = CancelBag() + let userType: UserType = .visitor let productServiceList: [HomePresentationModel.ProductService] = [ - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.homePage, image: DSKitAsset.Assets.imgHomepage.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.activityReview, image: DSKitAsset.Assets.imgGroupLogo.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.project, image: DSKitAsset.Assets.imgMemberLogo.image), - HomePresentationModel.ProductService(name: I18N.Home.MainProduct.instagram, image: DSKitAsset.Assets.imgInstagram.image) + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.homePage, image: DSKitAsset.Assets.imgHomepage.image, url: ExternalURL.SOPT.officialHomepage), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.activityReview, image: DSKitAsset.Assets.imgGroupLogo.image, url: ExternalURL.SOPT.review), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.project, image: DSKitAsset.Assets.imgMemberLogo.image, url: ExternalURL.SOPT.project), + HomePresentationModel.ProductService(name: I18N.Home.MainProduct.instagram, image: DSKitAsset.Assets.imgInstagram.image, url: ExternalURL.SNS.instagram) ] - - // MARK: - Properties - private let useCase: HomeUseCase - private var cancelBag = CancelBag() // MARK: - Inputs public struct Input { let viewDidLoad: Driver + let cellTapped: Driver + let settingButtonTapped: Driver } // MARK: - Outputs public struct Output { let appService = PassthroughSubject<[HomePresentationModel.AppService], Never>() + let isLoading = PassthroughSubject() } + // MARK: - HomeForVisitorCoordinating + + public var onMainProductCellTapped: ((String) -> Void)? + public var onAppServiceCellTapped: (() -> Void)? + public var onSettingButtonTapped: ((UserType) -> Void)? + // MARK: - initialization public init(useCase: HomeUseCase) { @@ -54,17 +61,41 @@ public class HomeForVisitorViewModel: HomeForVisitorViewModelType { } extension HomeForVisitorViewModel { - public func transform(from input: Input, cancelBag: Core.CancelBag) -> Output { + public func transform(from input: Input, cancelBag: CancelBag) -> Output { let output = Output() input.viewDidLoad + .handleEvents(receiveOutput: { + output.isLoading.send(true) + }) .flatMap(useCase.getAppServices) .withUnretained(self) .sink { owner, appServiceModel in let appService = appServiceModel.map { $0.toPresentation() } output.appService.send(appService) + output.isLoading.send(false) }.store(in: cancelBag) + input.cellTapped + .withUnretained(self) + .sink { owner, item in + switch item { + case .appService: + owner.onAppServiceCellTapped?() + case .productService(let model): + owner.onMainProductCellTapped?(model.url) + default: break + } + } + .store(in: cancelBag) + + input.settingButtonTapped + .withUnretained(self) + .sink { owner, _ in + owner.onSettingButtonTapped?(owner.userType) + } + .store(in: cancelBag) + return output } } diff --git a/SOPT-iOS/Projects/Features/RootFeature/Sources/ApplicationCoordinator.swift b/SOPT-iOS/Projects/Features/RootFeature/Sources/ApplicationCoordinator.swift index 484299a5..bc05cc60 100644 --- a/SOPT-iOS/Projects/Features/RootFeature/Sources/ApplicationCoordinator.swift +++ b/SOPT-iOS/Projects/Features/RootFeature/Sources/ApplicationCoordinator.swift @@ -22,6 +22,7 @@ import AttendanceFeature import DailySoptuneFeature import WebFeature import SoptlogFeature +import HomeFeature public final class ApplicationCoordinator: BaseCoordinator { @@ -129,7 +130,7 @@ extension ApplicationCoordinator { private func checkDidSignIn() { let needAuth = UserDefaultKeyList.Auth.appAccessToken == nil - needAuth ? runSignInFlow(by: .root) : runMainFlow() + needAuth ? runSignInFlow(by: .root) : runHomeFlow() } } @@ -139,7 +140,7 @@ extension ApplicationCoordinator { private func runSignInFlow(by style: CoordinatorStartingOption) { let coordinator = AuthCoordinator(router: router, factory: AuthBuilder()) coordinator.finishFlow = { [weak self, weak coordinator] userType in - self?.runMainFlow(type: userType) + self?.runHomeFlow(type: userType) self?.removeDependency(coordinator) } addDependency(coordinator) @@ -150,7 +151,7 @@ extension ApplicationCoordinator { childCoordinators = [] let coordinator = AuthCoordinator(router: router, factory: AuthBuilder(), url: url) coordinator.finishFlow = { [weak self, weak coordinator] userType in - self?.runMainFlow(type: userType) + self?.runHomeFlow(type: userType) self?.removeDependency(coordinator) } addDependency(coordinator) @@ -197,18 +198,45 @@ extension ApplicationCoordinator { coordinator.start() } - internal func runHomeFlow(type: UserType) { + internal func runHomeFlow(type: UserType? = nil) { + defer { + bindNotification() + } + + self.childCoordinators = [] + + let userType = type ?? UserDefaultKeyList.Auth.getUserType() let coordinator = HomeCoordinator( router: router, factory: HomeBuilder(), - userType: type + userType: userType ) coordinator.finishFlow = { [weak self, weak coordinator] in self?.removeDependency(coordinator) } - coordinator.requestCoordinating = { [weak self] in - self?.runAttendanceFlow() + coordinator.requestCoordinating = { [weak self, weak coordinator] destination in + switch destination { + case .calendar: + self?.runAttendanceFlow() + case .attendance: + self?.runAttendanceFlow() + case .setting(let userType): + self?.runMyPageFlow(of: userType) + case .signIn: + self?.runSignInFlow(by: .rootWindow(animated: true, message: nil)) + self?.removeDependency(coordinator) + case .notification: + self?.runNotificationFlow() + case .soptlog: + self?.runSoptlogFlow() + case .deepLink(let url): + self?.notificationHandler.receive(deepLink: url) + guard let deepLink = self?.notificationHandler.deepLink.value else { return } + self?.handleDeepLink(deepLink: deepLink) + case .webLink(let url): + self?.handleWebLink(webLink: url) + } } addDependency(coordinator) @@ -231,7 +259,7 @@ extension ApplicationCoordinator { return coordinator } - + @discardableResult internal func runStampFlow() -> StampCoordinator { let coordinator = StampCoordinator( diff --git a/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift b/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift index db8a250c..f473e905 100644 --- a/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift +++ b/SOPT-iOS/Projects/SOPT-iOS/Sources/Dependency/RegisterDependencies.swift @@ -175,7 +175,8 @@ extension AppDelegate { implement: { HomeRepository( homeService: DefaultHomeService(), - calendarService: DefaultCalendarService() + calendarService: DefaultCalendarService(), + userService: DefaultUserService() ) } )