Skip to content

[FD1 - 2] 서버와 로컬 간의 폴더 정보를 동기화 한다. #134

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: client/develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions client/Projects/OpenList/OpenList.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@
5F93491E2B16DFF300282545 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F93491D2B16DFF300282545 /* UICollectionView+Extensions.swift */; };
5F9349212B16E03400282545 /* PrivateDetailCheckListAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F93491F2B16E03400282545 /* PrivateDetailCheckListAction.swift */; };
5F9349222B16E03400282545 /* PrivateDetailCheckListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349202B16E03400282545 /* PrivateDetailCheckListRouter.swift */; };
5F9349242B17014700282545 /* FolderResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349232B17014700282545 /* FolderResponseDTO.swift */; };
5F9349262B17016000282545 /* UserResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349252B17016000282545 /* UserResponseDTO.swift */; };
5F9349282B17028100282545 /* FolderRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349272B17028100282545 /* FolderRepository.swift */; };
5F93492A2B1702A000282545 /* CheckListFolderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349292B1702A000282545 /* CheckListFolderItem.swift */; };
5F93492C2B1702CF00282545 /* DefaultFolderRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F93492B2B1702CF00282545 /* DefaultFolderRepository.swift */; };
5F93492E2B1704D200282545 /* PrivateCheckListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F93492D2B1704D200282545 /* PrivateCheckListUseCase.swift */; };
5F9349322B17129400282545 /* AccessTokenInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9349312B17129400282545 /* AccessTokenInterceptor.swift */; };
5FA1F88E2AFF7DA400869079 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5FA1F88D2AFF7DA400869079 /* Assets.xcassets */; };
5FA1F8912AFF7DA400869079 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5FA1F88F2AFF7DA400869079 /* LaunchScreen.storyboard */; };
5FA1F8A62AFF7E6C00869079 /* AppComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1F8A52AFF7E6C00869079 /* AppComponent.swift */; };
Expand Down Expand Up @@ -233,6 +240,13 @@
5F93491D2B16DFF300282545 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = "<group>"; };
5F93491F2B16E03400282545 /* PrivateDetailCheckListAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateDetailCheckListAction.swift; sourceTree = "<group>"; };
5F9349202B16E03400282545 /* PrivateDetailCheckListRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateDetailCheckListRouter.swift; sourceTree = "<group>"; };
5F9349232B17014700282545 /* FolderResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderResponseDTO.swift; sourceTree = "<group>"; };
5F9349252B17016000282545 /* UserResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserResponseDTO.swift; sourceTree = "<group>"; };
5F9349272B17028100282545 /* FolderRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderRepository.swift; sourceTree = "<group>"; };
5F9349292B1702A000282545 /* CheckListFolderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckListFolderItem.swift; sourceTree = "<group>"; };
5F93492B2B1702CF00282545 /* DefaultFolderRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultFolderRepository.swift; sourceTree = "<group>"; };
5F93492D2B1704D200282545 /* PrivateCheckListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateCheckListUseCase.swift; sourceTree = "<group>"; };
5F9349312B17129400282545 /* AccessTokenInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenInterceptor.swift; sourceTree = "<group>"; };
5FA1F8812AFF7DA200869079 /* OpenList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenList.app; sourceTree = BUILT_PRODUCTS_DIR; };
5FA1F88D2AFF7DA400869079 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5FA1F8902AFF7DA400869079 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -382,6 +396,7 @@
78C2F0482B05C00800E4EC4E /* DefaultCheckListRepository.swift */,
78A4C7282B0F5F9300E07492 /* DefaultAuthRepository.swift */,
4DDC433D2B0F2A3D00859B28 /* DefaultCRDTRepository.swift */,
5F93492B2B1702CF00282545 /* DefaultFolderRepository.swift */,
);
path = Repositories;
sourceTree = "<group>";
Expand Down Expand Up @@ -458,6 +473,7 @@
4D696CBA2B0F946100873B3B /* EditText.swift */,
4D8AEBD92B122AA400292AF3 /* CRDTRequestDTO.swift */,
4D8AEBDB2B122AAD00292AF3 /* CRDTResponseDTO.swift */,
5F9349292B1702A000282545 /* CheckListFolderItem.swift */,
);
path = Entites;
sourceTree = "<group>";
Expand Down Expand Up @@ -537,6 +553,8 @@
children = (
78A4C7262B0F5E4300E07492 /* LoginResponseDTO.swift */,
5F70C84A2B05FE9100826B5D /* PrivateCheckListResponseDTO.swift */,
5F9349232B17014700282545 /* FolderResponseDTO.swift */,
5F9349252B17016000282545 /* UserResponseDTO.swift */,
);
path = DTO;
sourceTree = "<group>";
Expand Down Expand Up @@ -640,6 +658,7 @@
78F7476C2B126EB7003ED6DA /* KeyChain */,
4DCF10EE2B10EC3900C614B7 /* DataStructure */,
5FA1F8AE2AFF7FAD00869079 /* App */,
5F9349312B17129400282545 /* AccessTokenInterceptor.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -673,6 +692,7 @@
78C2F0232B049AF100E4EC4E /* CheckListRepository.swift */,
78A4C72A2B0F5FB100E07492 /* AuthRepository.swift */,
4DDC43282B0F10EF00859B28 /* CRDTRepository.swift */,
5F9349272B17028100282545 /* FolderRepository.swift */,
);
path = Repositories;
sourceTree = "<group>";
Expand All @@ -685,6 +705,7 @@
78A4C7242B0F5C6900E07492 /* AuthUseCase.swift */,
4DDC43252B0F0FAB00859B28 /* CRDTUseCase.swift */,
4DD97D9B2B14D3A600DF73BC /* DetailCheckListUseCase.swift */,
5F93492D2B1704D200282545 /* PrivateCheckListUseCase.swift */,
);
path = UseCases;
sourceTree = "<group>";
Expand Down Expand Up @@ -970,6 +991,7 @@
4DDC433E2B0F2A3D00859B28 /* DefaultCRDTRepository.swift in Sources */,
5FA1F8B62AFF7FBB00869079 /* Dependency.swift in Sources */,
78A4C72B2B0F5FB100E07492 /* AuthRepository.swift in Sources */,
5F93492C2B1702CF00282545 /* DefaultFolderRepository.swift in Sources */,
78C2F00F2B048B6C00E4EC4E /* TabBarViewController.swift in Sources */,
78C2F0222B049A8A00E4EC4E /* PersistenceUseCase.swift in Sources */,
4D8AEBDA2B122AA400292AF3 /* CRDTRequestDTO.swift in Sources */,
Expand All @@ -984,10 +1006,12 @@
5FA1F8B42AFF7FBB00869079 /* ViewControllable.swift in Sources */,
5F93490C2B16DF4D00282545 /* CheckListTopTabView.swift in Sources */,
5F8615E02B02249C00CF2686 /* ViewModelable.swift in Sources */,
5F9349282B17028100282545 /* FolderRepository.swift in Sources */,
4DDC434B2B0F432900859B28 /* Device.swift in Sources */,
5FA1F8A92AFF7F6700869079 /* AppDelegate.swift in Sources */,
4D967CF12B05AF940032E0D7 /* CoreDataStorage.swift in Sources */,
78A4C7252B0F5C6900E07492 /* AuthUseCase.swift in Sources */,
5F9349322B17129400282545 /* AccessTokenInterceptor.swift in Sources */,
5F93491E2B16DFF300282545 /* UICollectionView+Extensions.swift in Sources */,
5F9349162B16DF5500282545 /* CheckListFolderRouter.swift in Sources */,
4DD97D9C2B14D3A600DF73BC /* DetailCheckListUseCase.swift in Sources */,
Expand All @@ -1007,6 +1031,7 @@
78A4C71D2B0F2ED900E07492 /* LoginViewFactory.swift in Sources */,
5FA1F8E32AFF818E00869079 /* TabBarViewFactory.swift in Sources */,
78A4C71B2B0F2ED900E07492 /* LoginRouter.swift in Sources */,
5F9349242B17014700282545 /* FolderResponseDTO.swift in Sources */,
4D3398082B023BE800963664 /* CheckListTableAction.swift in Sources */,
5F70C83D2B04C39F00826B5D /* CheckListItemTextField.swift in Sources */,
4D967CD42B050EA60032E0D7 /* UIFont+.swift in Sources */,
Expand All @@ -1015,6 +1040,7 @@
5F93491C2B16DFE300282545 /* CALayer+applySketchShadow.swift in Sources */,
78A4C7292B0F5F9300E07492 /* DefaultAuthRepository.swift in Sources */,
4DDC43322B0F16CC00859B28 /* WithDetailCheckListViewFactory.swift in Sources */,
5F93492A2B1702A000282545 /* CheckListFolderItem.swift in Sources */,
4D967CF42B05AF9F0032E0D7 /* CoreDataStorage.xcdatamodeld in Sources */,
5F93490B2B16DF4D00282545 /* CheckListTabViewFactory.swift in Sources */,
5FA1F8D22AFF802900869079 /* RecommendTabRouter.swift in Sources */,
Expand All @@ -1024,6 +1050,7 @@
4DDC43292B0F10EF00859B28 /* CRDTRepository.swift in Sources */,
5FA1F8D12AFF802900869079 /* RecommendTabViewController.swift in Sources */,
5FCCA3C12B104ABF00496EB2 /* OpenListNavigationBar.swift in Sources */,
5F93492E2B1704D200282545 /* PrivateCheckListUseCase.swift in Sources */,
5F70C8392B04BF7A00826B5D /* LocalCheckListItem.swift in Sources */,
5F9349022B16DF4600282545 /* SharedCheckListViewController.swift in Sources */,
5FA8466B2B10A08B00B90F85 /* UIDevice+safeAreaHeight.swift in Sources */,
Expand All @@ -1046,6 +1073,7 @@
5F9349192B16DF5500282545 /* CheckListFolderAction.swift in Sources */,
4DDC43422B0F2AC000859B28 /* CRDTStorage.swift in Sources */,
5F70C8402B04C6F000826B5D /* UITableView+Extensions.swift in Sources */,
5F9349262B17016000282545 /* UserResponseDTO.swift in Sources */,
4D3398172B024FEE00963664 /* GaugeView.swift in Sources */,
78A4C71E2B0F2ED900E07492 /* LoginViewModel.swift in Sources */,
5FA1F8B32AFF7FBB00869079 /* Component.swift in Sources */,
Expand Down
24 changes: 24 additions & 0 deletions client/Projects/OpenList/OpenList/Data/DTO/FolderResponseDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// FolderResponseDTO.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import Foundation

struct FolderResponseDTO: Decodable {
let folderId: Int
let title: String
let user: UserResponseDTO
let createdAt: String
let updatedAt: String

enum CodingKeys: String, CodingKey {
case folderId
case title
case user = "owner"
case createdAt
case updatedAt
}
}
20 changes: 20 additions & 0 deletions client/Projects/OpenList/OpenList/Data/DTO/UserResponseDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// UserResponseDTO.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import Foundation

struct UserResponseDTO: Decodable {
let userId: Int
let email: String
let providerId: String
let provider: String
let fullName: String
let nickname: String
let profileImage: String
let createdAt: String
let updatedAt: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// DefaultFolderRepository.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import CustomNetwork
import Foundation

final class DefaultFolderRepository {
private let session: CustomSession

init(session: CustomSession = CustomSession(configuration: .default, interceptor: AccessTokenInterceptor())) {
self.session = session
}
}

extension DefaultFolderRepository: FolderRepository {
enum Constant {
static let baseUrl = "https://openlist.kro.kr/folders"
}

func fetchAllFolders() async throws -> [CheckListFolderItem] {
var builder = URLRequestBuilder(url: Constant.baseUrl)
builder.addHeader(
field: "Content-Type",
value: "application/json"
)
let service = NetworkService(customSession: session, urlRequestBuilder: builder)
let data = try await service.getData()
Copy link
Member

@SeongHunTed SeongHunTed Nov 29, 2023

Choose a reason for hiding this comment

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

영균님 getData 다음 PR에서 사라지지 않나요? 다음 PR에서 수정할까요? 아니면 Network 모듈 PR 닫고 수정할까요?

let folderResponseDTO = try JSONDecoder().decode([FolderResponseDTO].self, from: data)
let folders = folderResponseDTO.map { CheckListFolderItem(folderId: $0.folderId, title: $0.title) }
return folders
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// CheckListFolderItem.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import Foundation

struct CheckListFolderItem: Hashable {
private var id: Int { folderId }
let folderId: Int
let title: String
var saveCount: Int = 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// FolderRepository.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import Foundation

protocol FolderRepository {
func fetchAllFolders() async throws -> [CheckListFolderItem]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// PrivateCheckListUseCase.swift
// OpenList
//
// Created by 김영균 on 11/29/23.
//

import Foundation

protocol PrivateCheckListUseCase {
func fetchAllFolders() async -> Result<[CheckListFolderItem], Error>
Copy link
Member

Choose a reason for hiding this comment

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

오 저는 기존에 Bool Type 리턴했는데 Result Type 사용해야겠네용

}

final class DefaultPrivateCheckListUseCase {
private let folderRepository: FolderRepository

init(folderRepository: FolderRepository) {
self.folderRepository = folderRepository
}
}

extension DefaultPrivateCheckListUseCase: PrivateCheckListUseCase {
func fetchAllFolders() async -> Result<[CheckListFolderItem], Error> {
do {
let folders = try await folderRepository.fetchAllFolders()
return .success(folders)
} catch {
return .failure(error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

import Combine

struct CheckListFolderInput { }
struct CheckListFolderInput {
let viewWillAppear: PassthroughSubject<Void, Never>
}

enum CheckListFolderState { }
enum CheckListFolderState {
case folders([CheckListFolderItem])
case error(Error)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ final class CheckListFolderViewController: UIViewController, ViewControllable {
}
// MARK: - Properties
private let router: CheckListFolderRoutingLogic
private let viewModel: any ViewModelable
private let viewModel: any CheckListFolderViewModelable
private var cancellables: Set<AnyCancellable> = []
// Event Properties
private var viewWillAppear: PassthroughSubject<Void, Never> = .init()

// MARK: - UI Components
private let folderView: UICollectionView = .init(frame: .zero, collectionViewLayout: .init())
Expand All @@ -26,7 +28,7 @@ final class CheckListFolderViewController: UIViewController, ViewControllable {
// MARK: - Initializers
init(
router: CheckListFolderRoutingLogic,
viewModel: some ViewModelable
viewModel: some CheckListFolderViewModelable
) {
self.router = router
self.viewModel = viewModel
Expand All @@ -47,18 +49,48 @@ final class CheckListFolderViewController: UIViewController, ViewControllable {
setViewHierarchies()
setViewConstraints()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewWillAppear.send()
}
}

// MARK: - Bind Methods
extension CheckListFolderViewController: ViewBindable {
typealias State = CheckListFolderState
typealias OutputError = Error

func bind() {}
func bind() {
let input = CheckListFolderInput(viewWillAppear: viewWillAppear)
let state = viewModel.transform(input)
state
.receive(on: DispatchQueue.main)
.withUnretained(self)
.sink { (owner, state) in owner.render(state)}
.store(in: &cancellables)
}

func render(_ state: State) { }
func render(_ state: State) {
switch state {
case let .folders(folders):
reloadFolders(folders)
case let .error(error):
handleError(error)
}
}

func handleError(_ error: OutputError) { }
func handleError(_ error: OutputError) {
dump(error.localizedDescription)
}

func reloadFolders(_ folders: [CheckListFolderItem]) {
guard var snapshot = folderViewDataSource?.snapshot() else { return }
let previousItems = snapshot.itemIdentifiers(inSection: .folder)
snapshot.deleteItems(previousItems)
snapshot.appendItems(folders)
folderViewDataSource?.apply(snapshot)
}
}

// MARK: - View Methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@

import Foundation

protocol CheckListFolderDependency: Dependency { }
protocol CheckListFolderDependency: Dependency {
var folderRepository: FolderRepository { get }
}

final class CheckListFolderComponent: Component<CheckListFolderDependency> { }
final class CheckListFolderComponent: Component<CheckListFolderDependency> {
fileprivate var privateCheckListUseCase: PrivateCheckListUseCase {
DefaultPrivateCheckListUseCase(folderRepository: parent.folderRepository)
}
}

protocol CheckListFolderFactoryable: Factoryable {
func make() -> ViewControllable
Expand All @@ -21,8 +27,9 @@ final class CheckListFolderViewFactory: Factory<CheckListFolderDependency>, Chec
}

func make() -> ViewControllable {
let component = CheckListFolderComponent(parent: parent)
let router = CheckListFolderRouter()
let viewModel = CheckListFolderViewModel()
let viewModel = CheckListFolderViewModel(privateCheckListUseCase: component.privateCheckListUseCase)
let viewController = CheckListFolderViewController(router: router, viewModel: viewModel)
router.viewController = viewController
return viewController
Expand Down
Loading