diff --git a/Projects/Core/PPACData/Sources/DTO/MemeResponseDTO.swift b/Projects/Core/PPACData/Sources/DTO/MemeResponseDTO.swift index 191f86d..2aa52ba 100644 --- a/Projects/Core/PPACData/Sources/DTO/MemeResponseDTO.swift +++ b/Projects/Core/PPACData/Sources/DTO/MemeResponseDTO.swift @@ -70,7 +70,7 @@ struct MemeResponseDTO: Decodable { updatedAt: String, isSaved: Bool, isReaction: Bool, - watch: Int + watch: Int? ) { self._id = _id diff --git a/Projects/Core/PPACData/Sources/Endpoint/UserEndpoint.swift b/Projects/Core/PPACData/Sources/Endpoint/UserEndpoint.swift index 7e58bd5..a82f537 100644 --- a/Projects/Core/PPACData/Sources/Endpoint/UserEndpoint.swift +++ b/Projects/Core/PPACData/Sources/Endpoint/UserEndpoint.swift @@ -13,18 +13,19 @@ public enum UserEndpoint: Requestable { case create(deviceId: String) case userDetail - case savedMeme(page: Int, size: Int) case lastSeenMeme + case savedMeme(page: Int, size: Int) + case registeredMemes(page: Int, size: Int) + public var httpMethod: PPACNetwork.HTTPMethod { switch self { case .create: return .post - case .userDetail: - return .get - case .savedMeme: - return .get - case .lastSeenMeme: + case .userDetail, + .lastSeenMeme, + .savedMeme, + .registeredMemes: return .get } } @@ -39,10 +40,12 @@ public enum UserEndpoint: Requestable { return "/user" case .userDetail: return "/user" - case .savedMeme: - return "/user/saved-memes" case .lastSeenMeme: return "/user/recent-memes" + case .savedMeme: + return "/user/saved-memes" + case .registeredMemes: + return "/user/registered-memes" } } @@ -57,6 +60,12 @@ public enum UserEndpoint: Requestable { "size" : "\(size)" ] return .query(parameters) + case .registeredMemes(let page, let size): + let parameters: [String: String] = [ + "page" : "\(page)", + "size" : "\(size)" + ] + return .query(parameters) default: return nil } diff --git a/Projects/Core/PPACData/Sources/Repository/UserRepositoryImpl.swift b/Projects/Core/PPACData/Sources/Repository/UserRepositoryImpl.swift index a9aa1d3..78b5b2f 100644 --- a/Projects/Core/PPACData/Sources/Repository/UserRepositoryImpl.swift +++ b/Projects/Core/PPACData/Sources/Repository/UserRepositoryImpl.swift @@ -54,6 +54,22 @@ public final class UserRepositoryImpl: UserRepository { } } + public func getLastSeenMeme() async throws -> [MemeDetail] { + let result = await networkservice + .request( + UserEndpoint.lastSeenMeme, + dataType: BaseDTO<[MemeResponseDTO]>.self + ) + + switch result { + case .success(let data): + guard let memeResponseDTOList = data.data else { throw NetworkError.dataDecodingError } + return memeResponseDTOList.map { $0.toModel() } + case .failure(let error): + throw error + } + } + public func getSavedMeme(page: Int, size: Int) async throws -> MemeListWithPagination { let result = await networkservice .request( @@ -64,7 +80,7 @@ public final class UserRepositoryImpl: UserRepository { switch result { case .success(let data): guard let memeWithPaginationResponseDTO = data.data else { throw NetworkError.dataDecodingError } - var result = memeWithPaginationResponseDTO.toModel() + let result = memeWithPaginationResponseDTO.toModel() let memeList = result.memeList .map { var meme = $0 @@ -80,17 +96,27 @@ public final class UserRepositoryImpl: UserRepository { } } - public func getLastSeenMeme() async throws -> [MemeDetail] { + public func getRegisteredMeme(page: Int, size: Int) async throws -> MemeListWithPagination { let result = await networkservice .request( - UserEndpoint.lastSeenMeme, - dataType: BaseDTO<[MemeResponseDTO]>.self + UserEndpoint.registeredMemes(page: page, size: size), + dataType: BaseDTO.self ) switch result { case .success(let data): - guard let memeResponseDTOList = data.data else { throw NetworkError.dataDecodingError } - return memeResponseDTOList.map { $0.toModel() } + guard let memeWithPaginationResponseDTO = data.data else { throw NetworkError.dataDecodingError } + let result = memeWithPaginationResponseDTO.toModel() + let memeList = result.memeList + .map { + var meme = $0 + meme.reaction = 0 + return meme + } + return MemeListWithPagination( + pagination: result.pagination, + memeList: memeList + ) case .failure(let error): throw error } diff --git a/Projects/Core/PPACDomain/Sources/Repository/UserRepository.swift b/Projects/Core/PPACDomain/Sources/Repository/UserRepository.swift index c849b8c..ea84d84 100644 --- a/Projects/Core/PPACDomain/Sources/Repository/UserRepository.swift +++ b/Projects/Core/PPACDomain/Sources/Repository/UserRepository.swift @@ -12,6 +12,7 @@ import PPACModels public protocol UserRepository { func create(deviceId: String) async throws -> UserDetail func getUserDetail() async throws -> UserDetail - func getSavedMeme(page: Int, size: Int) async throws -> MemeListWithPagination func getLastSeenMeme() async throws -> [MemeDetail] + func getSavedMeme(page: Int, size: Int) async throws -> MemeListWithPagination + func getRegisteredMeme(page: Int, size: Int) async throws -> MemeListWithPagination } diff --git a/Projects/Core/PPACDomain/Sources/UseCase/CheckUserInfoUseCase.swift b/Projects/Core/PPACDomain/Sources/UseCase/CheckUserInfoUseCase.swift index e5a170f..0981e68 100644 --- a/Projects/Core/PPACDomain/Sources/UseCase/CheckUserInfoUseCase.swift +++ b/Projects/Core/PPACDomain/Sources/UseCase/CheckUserInfoUseCase.swift @@ -62,6 +62,7 @@ public class MockCheckUserInfoUseCase: CheckUserInfoUseCase { func create(deviceId: String) async throws -> UserDetail { return UserDetail.mock } func getUserDetail() async throws -> UserDetail { return UserDetail.mock } func getSavedMeme(page: Int, size: Int) async throws -> MemeListWithPagination { return MemeListWithPagination.mock } + func getRegisteredMeme(page: Int, size: Int) async throws -> MemeListWithPagination { return MemeListWithPagination.mock } func getLastSeenMeme() async throws -> [MemeDetail] { return [] } } } diff --git a/Projects/Core/PPACDomain/Sources/UseCase/Search/GetRegisteredMemeUseCase.swift b/Projects/Core/PPACDomain/Sources/UseCase/Search/GetRegisteredMemeUseCase.swift new file mode 100644 index 0000000..3c99bae --- /dev/null +++ b/Projects/Core/PPACDomain/Sources/UseCase/Search/GetRegisteredMemeUseCase.swift @@ -0,0 +1,26 @@ +// +// GetRegisteredMemeUseCase.swift +// PPACDomain +// +// Created by 장혜령 on 10/2/24. +// + +import Foundation +import PPACModels + +public protocol GetRegisteredMemeUseCase { + func execute(page: Int, size: Int) async throws -> MemeListWithPagination +} + +public class GetRegisteredMemeUseCaseImpl: GetRegisteredMemeUseCase { + public let userRepository: UserRepository + + public init(userRepository: UserRepository) { + self.userRepository = userRepository + } + + public func execute(page: Int, size: Int) async throws -> MemeListWithPagination { + return try await self.userRepository.getRegisteredMeme(page: page, size: size) + } +} + diff --git a/Projects/Features/MyPage/Sources/MyPageRouter.swift b/Projects/Features/MyPage/Sources/MyPageRouter.swift index 975dd19..0acf3b7 100644 --- a/Projects/Features/MyPage/Sources/MyPageRouter.swift +++ b/Projects/Features/MyPage/Sources/MyPageRouter.swift @@ -51,6 +51,7 @@ public final class MyPageRouter: Router, MyPageRouting { getUserDetailUseCase: GetUserDetailUseCaseImpl(userRepository: repository), getLastSeenMemeUseCase: GetLastSeenMemeUseCaseImpl(userRepository: repository), getSavedMemeUseCase: GetSavedMemeUseCaseImpl(userRepository: repository), + getRegisteredMemeUseCase: GetRegisteredMemeUseCaseImpl(userRepository: repository), copyImageUseCase: CopyImageUseCaseImpl() ) ).tabBar(selectedTab: selectedTab) diff --git a/Projects/Features/MyPage/Sources/MyPageView.swift b/Projects/Features/MyPage/Sources/MyPageView.swift index 2397e03..d5b3197 100644 --- a/Projects/Features/MyPage/Sources/MyPageView.swift +++ b/Projects/Features/MyPage/Sources/MyPageView.swift @@ -37,7 +37,7 @@ public struct MyPageView: View { viewModel.dispatch(type: .onTappedSegmentedTitleItem(title: title)) } ) - SavedMemeListView( + MemeListView( memeDetailList: $viewModel.state.currentMyMemeList, memeClickHandler: { meme in viewModel.dispatch(type: .onTappedSavedMeme(meme: meme)) @@ -49,7 +49,21 @@ public struct MyPageView: View { viewModel.dispatch(type: .onAppearLastMeme) } ) + .padding(.horizontal, 20) .padding(.bottom, 138) +// SavedMemeListView( +// memeDetailList: $viewModel.state.currentMyMemeList, +// memeClickHandler: { meme in +// viewModel.dispatch(type: .onTappedSavedMeme(meme: meme)) +// }, +// memeCopyHandler: { meme in +// viewModel.dispatch(type: .onTappedCopyButton(meme: meme)) +// }, +// onAppearLastMemeHandler: { +// viewModel.dispatch(type: .onAppearLastMeme) +// } +// ) +// .padding(.bottom, 138) } .onAppear { viewModel.dispatch(type: .onAppearMyPageView) diff --git a/Projects/Features/MyPage/Sources/MyPageViewModel.swift b/Projects/Features/MyPage/Sources/MyPageViewModel.swift index 21d7a48..607e652 100644 --- a/Projects/Features/MyPage/Sources/MyPageViewModel.swift +++ b/Projects/Features/MyPage/Sources/MyPageViewModel.swift @@ -21,11 +21,16 @@ public protocol MyPageRouting: AnyObject { final public class MyPageViewModel: ViewModelType, ObservableObject { // 트래킹을 위해 추가한 type - public enum MyMemeType: String { + enum MyMemeType: String { case recentMeme = "my_recent_meme" case savedMeme = "my_saved_meme" } + enum MyPageSegmentedTitle: String { + case myRegisteredMeme = "내가 올린 밈" + case mySavedMeme = "나의 파밈함" + } + public enum Action { case onAppearMyPageView case pullToRefresh @@ -42,25 +47,33 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { var lastSeenMemeList: [MemeDetail] var savedMemeList: [MemeDetail] var savedMemePagination: MemeListWithPagination.Pagination + var registeredMemeList: [MemeDetail] + var registeredMemePagination: MemeListWithPagination.Pagination var isRefreshCompleted: Bool var isActiveCopyPopup: Bool var currentMyMemeList: [MemeDetail] var segmentedTitleItems: [SegmentedTitleItem] = [] + var currentSegmentedTitle: MyPageSegmentedTitle init( userDetail: UserDetail, lastSeenMemeList: [MemeDetail], savedMemeList: [MemeDetail], - savedMemePagination: MemeListWithPagination.Pagination + savedMemePagination: MemeListWithPagination.Pagination, + registeredMemeList: [MemeDetail], + registeredMemePagination: MemeListWithPagination.Pagination ) { self.userDetail = userDetail self.lastSeenMemeList = lastSeenMemeList self.savedMemeList = savedMemeList self.savedMemePagination = savedMemePagination + self.registeredMemeList = registeredMemeList + self.registeredMemePagination = registeredMemePagination self.isRefreshCompleted = true self.isActiveCopyPopup = false - self.currentMyMemeList = lastSeenMemeList // TODO: 나중에 나의 밈으로 바뀌어야함 + self.currentMyMemeList = registeredMemeList + self.currentSegmentedTitle = .myRegisteredMeme self.segmentedTitleItems = initSegmentedTitleItems() } @@ -85,9 +98,13 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { return savedMemePagination.currentPage < savedMemePagination.totalPages } + var hesNextPageOfRegisteredMeme: Bool { + return registeredMemePagination.currentPage < registeredMemePagination.totalPages + } + private func initSegmentedTitleItems() -> [SegmentedTitleItem] { - return [SegmentedTitleItem(title: "나의 밈", isSelected: true), - SegmentedTitleItem(title: "나의 파밈함", isSelected: false)] + return [SegmentedTitleItem(title: MyPageSegmentedTitle.myRegisteredMeme.rawValue, isSelected: true), + SegmentedTitleItem(title: MyPageSegmentedTitle.mySavedMeme.rawValue, isSelected: false)] } } @@ -98,10 +115,10 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { private let getUserDetailUseCase: GetUserDetailUseCase private let getLastSeenMemeUseCase: GetLastSeenMemeUseCase private let getSavedMemeUseCase: GetSavedMemeUseCase + private let getRegisteredMemeUseCase: GetRegisteredMemeUseCase private let copyImageUseCase: CopyImageUseCase - private var currentPage: Int = 1 - private let savedMemeCountPerPage: Int = 10 + private let memeCountPerPage: Int = 10 // MARK: - Initializers @@ -111,17 +128,21 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { getUserDetailUseCase: GetUserDetailUseCase, getLastSeenMemeUseCase: GetLastSeenMemeUseCase, getSavedMemeUseCase: GetSavedMemeUseCase, + getRegisteredMemeUseCase: GetRegisteredMemeUseCase, copyImageUseCase: CopyImageUseCase ) { self.router = router self.state = State(userDetail: userDetail, lastSeenMemeList: [], savedMemeList: [], - savedMemePagination: .default) + savedMemePagination: .default, + registeredMemeList: [], + registeredMemePagination: .default) self.userDetail = userDetail self.getUserDetailUseCase = getUserDetailUseCase self.getLastSeenMemeUseCase = getLastSeenMemeUseCase self.getSavedMemeUseCase = getSavedMemeUseCase + self.getRegisteredMemeUseCase = getRegisteredMemeUseCase self.copyImageUseCase = copyImageUseCase } @@ -146,9 +167,9 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { await self.copyMemeImage(with: meme) case .onTappedSegmentedTitleItem(let title): self.updateSegmentedTitleItems(selectedTitle: title) - self.updateCurrentMyMemeList(selectedTitle: title) + self.updateCurrentMyMemeList(selectedTitle: MyPageSegmentedTitle(rawValue: title)) case .onAppearLastMeme: - await self.fetchNextPageSavedMeme() + await self.fetchNextPageMemes() } } } @@ -158,12 +179,15 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { do { let userDetail = try await self.getUserDetailUseCase.execute() let lastSeenMemeList = try await self.getLastSeenMemeUseCase.execute() - let savedMemeListWithPagination = try await self.getSavedMemeUseCase.execute(page: 1, - size: self.savedMemeCountPerPage) + let savedMemeListWithPagination = try await self.getSavedMemeUseCase.execute(page: 1,size: self.memeCountPerPage) + let registeredMemeListWithPagination = try await self.getRegisteredMemeUseCase.execute(page: 1, size: self.memeCountPerPage) + self.state = State(userDetail: userDetail, lastSeenMemeList: lastSeenMemeList, savedMemeList: savedMemeListWithPagination.memeList, - savedMemePagination: savedMemeListWithPagination.pagination) + savedMemePagination: savedMemeListWithPagination.pagination, + registeredMemeList: registeredMemeListWithPagination.memeList, + registeredMemePagination: registeredMemeListWithPagination.pagination) self.logMyPage( interaction: .scroll, @@ -182,6 +206,15 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { await fetchUserMemes() } + @MainActor + private func fetchNextPageMemes() async { + if self.state.currentSegmentedTitle == .myRegisteredMeme { + await self.fetchNextPageRegisteredMeme() + } else { + await self.fetchNextPageSavedMeme() + } + } + @MainActor private func fetchNextPageSavedMeme() async { guard state.hasNextPageOfSavedMeme else { return } @@ -190,11 +223,11 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { let savedMemeListWithPagination = try await self.getSavedMemeUseCase .execute( page: state.savedMemePagination.currentPage + 1, - size: self.savedMemeCountPerPage + size: self.memeCountPerPage ) self.state.savedMemeList += savedMemeListWithPagination.memeList self.state.savedMemePagination = savedMemeListWithPagination.pagination - + self.updateCurrentMyMemeList(selectedTitle: .mySavedMeme) self.logMyPage( interaction: .scroll, event: .meme, @@ -206,6 +239,30 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { } } + @MainActor + private func fetchNextPageRegisteredMeme() async { + guard state.hesNextPageOfRegisteredMeme else { return } + + do { + let registeredMemeListWithPagination = try await self.getRegisteredMemeUseCase + .execute( + page: self.state.registeredMemePagination.currentPage + 1, + size: self.memeCountPerPage + ) + self.state.registeredMemeList += registeredMemeListWithPagination.memeList + self.state.registeredMemePagination = registeredMemeListWithPagination.pagination + self.updateCurrentMyMemeList(selectedTitle: .myRegisteredMeme) + self.logMyPage( + interaction: .scroll, + event: .meme, + pageCount: state.registeredMemePagination.currentPage + ) + + } catch(let error) { + print("fetchNextPageSavedMeme error = \(error)") + } + } + @MainActor private func copyMemeImage(with meme: MemeDetail?) async { guard let meme else { return } @@ -233,11 +290,14 @@ final public class MyPageViewModel: ViewModelType, ObservableObject { self.state.segmentedTitleItems = newTitleItems } - private func updateCurrentMyMemeList(selectedTitle: String) { - if selectedTitle == "나의 밈" { - self.state.currentMyMemeList = self.state.lastSeenMemeList + private func updateCurrentMyMemeList(selectedTitle: MyPageSegmentedTitle?) { + guard let selectedTitle else { return } + if selectedTitle == .myRegisteredMeme { + self.state.currentMyMemeList = self.state.registeredMemeList + self.state.currentSegmentedTitle = .myRegisteredMeme } else { self.state.currentMyMemeList = self.state.savedMemeList + self.state.currentSegmentedTitle = .mySavedMeme } }