Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Mark-In/Sources/Domain/Entities/Folder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct Folder {
struct Folder: Equatable, Hashable {
var id: String
var name: String
var createdBy: Date
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// GenerateFolderUseCaseImpl.swift
// Mark-In
//
// Created by 이정동 on 5/8/25.
//

import Foundation

struct GenerateFolderUseCaseImpl: GenerateFolderUseCase {

private let folderRepository: FolderRepository

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

func execute(name: String) async throws -> Folder {
let writeFolder = WriteFolder(name: name)

// TODO: #29번 PR 머지 후 AuthManager를 통해 현재 로그인 유저 정보 가져옴
let user = "123"

let newFolder = try await folderRepository.create(userID: user, folder: writeFolder)
return newFolder
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// GenerateFolderUseCase.swift
// Mark-In
//
// Created by 이정동 on 5/8/25.
//

import Foundation

protocol GenerateFolderUseCase {
func execute(name: String) async throws -> Folder
}
99 changes: 99 additions & 0 deletions Mark-In/Sources/Feature/AddFolder/AddFolderView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// AddFolderView.swift
// Mark-In
//
// Created by 이정동 on 5/8/25.
//

import SwiftUI

import DesignSystem

struct AddFolderView: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel = AddFolderViewModel()
@State private var title: String = ""

private var isSaving: Bool {
viewModel.state.isLoading
}

let completion: (Folder) -> ()

var body: some View {
VStack(spacing: 0) {
Text("폴더를 추가:")
.frame(maxWidth: .infinity, alignment: .leading)

TextField("", text: $title, prompt: Text("제목"))
.textFieldStyle(.roundedBorder)
.padding(.top, 14)
.disabled(isSaving)

HStack {
if isSaving {
ProgressView()
.frame(width: 12, height: 12)
.scaleEffect(0.4, anchor: .center)
}

Button {
dismiss()
} label: {
Text("취소")
.padding(.vertical, 4)
.padding(.horizontal, 14)
.foregroundStyle(.markBlack)
.background(.markWhite)
.clipShape(RoundedRectangle(cornerRadius: 6))
.overlay {
RoundedRectangle(cornerRadius: 6)
.stroke(.markBlack10, lineWidth: 0.5)
}
}
.disabled(title.isEmpty || isSaving)

Button {
viewModel.send(.didTapAddLinkButton(title: title))
} label: {
Text("추가")
.padding(.vertical, 4)
.padding(.horizontal, 14)
.foregroundStyle(.markWhite)
.background(.markPoint)
.clipShape(RoundedRectangle(cornerRadius: 6))
}
.disabled(title.isEmpty || isSaving)
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.top, 18)
.font(.pretendard(size: 14, weight: .medium))
.buttonStyle(.plain)
}
.padding(20)
.frame(width: 400)
.onChange(of: viewModel.state.createdFolder) {
guard let folder = $1 else { return }
completion(folder)
dismiss()
}
.alert(
"폴더 생성에 실패했습니다.",
isPresented: .init(
get: { viewModel.state.isError },
set: { viewModel.send(.updateErrorState($0)) }
)
) {
Button(role: .cancel) {
} label: {
Text("확인")
}
}
}
}

#Preview {
AddFolderView() {
print($0)
}
}
76 changes: 76 additions & 0 deletions Mark-In/Sources/Feature/AddFolder/AddFolderViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// AddFolderViewModel.swift
// Mark-In
//
// Created by 이정동 on 5/8/25.
//

import Foundation

@Observable @MainActor
final class AddFolderViewModel: Reducer {
struct State {
var createdFolder: Folder?
var isLoading: Bool = false
var isError: Bool = false
}

enum Action {
case didTapAddLinkButton(title: String)
case didCompleteSave(Folder)
case updateErrorState(Bool)
}

private let generateFolderUseCase: GenerateFolderUseCase

private(set) var state: State = .init()

init() {
// TODO: DIContainer PR 머지 이후 DIContainer를 통해 의존성 주입
self.generateFolderUseCase = GenerateFolderUseCaseImpl(folderRepository: FolderRepositoryImpl())
}

func send(_ action: Action) {
let effect = reduce(state: &state, action: action)
handleEffect(effect)
}

func reduce(state: inout State, action: Action) -> Effect<Action> {
switch action {
case .didTapAddLinkButton(let title):
state.isLoading = true

return .run {
do {
let result = try await self.generateFolderUseCase.execute(name: title)
return .didCompleteSave(result)
} catch {
return .updateErrorState(true)
}
}

case .didCompleteSave(let folder):
state.isLoading = false
state.createdFolder = folder
return .none

case .updateErrorState(let bool):
state.isLoading = false
state.isError = bool
return .none
}
}

private func handleEffect(_ effect: Effect<Action>) {
switch effect {
case .none:
break
case .run(let action):
Task.detached { [weak self] in
let newAction = await action()
await self?.send(newAction)
}
}
}
}

10 changes: 5 additions & 5 deletions Mark-In/Sources/Feature/AddLink/AddLinkView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ struct AddLinkView: View {
Text("취소")
.padding(.vertical, 4)
.padding(.horizontal, 14)
.foregroundStyle(.black)
.background(.white)
.foregroundStyle(.markBlack)
.background(.markWhite)
.clipShape(RoundedRectangle(cornerRadius: 6))
.overlay {
RoundedRectangle(cornerRadius: 6)
.stroke(.gray, lineWidth: 0.5)
.stroke(.markBlack10, lineWidth: 0.5)
}
}

Expand All @@ -79,8 +79,8 @@ struct AddLinkView: View {
Text("추가")
.padding(.vertical, 4)
.padding(.horizontal, 14)
.foregroundStyle(.white)
.background(.blue)
.foregroundStyle(.markWhite)
.background(.markPoint)
.clipShape(RoundedRectangle(cornerRadius: 6))
}
}
Expand Down
7 changes: 1 addition & 6 deletions Mark-In/Sources/Feature/Common/SidebarTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ enum SidebarTab: Hashable {
case total
case pin
case nonRead
case folder(TestFolder)
case folder(Folder)

var title: String {
switch self {
Expand All @@ -30,9 +30,4 @@ enum SidebarTab: Hashable {
case .folder(_): "folder"
}
}

var isFolder: Bool {
if case .folder(_) = self { true }
else { false }
}
}
22 changes: 18 additions & 4 deletions Mark-In/Sources/Feature/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import DesignSystem
struct MainView: View {
@State private var viewModel = MainViewModel()
@State private var searchText: String = ""
@State private var isAddMode: Bool = false

var body: some View {
ZStack {
Expand Down Expand Up @@ -43,8 +42,12 @@ struct MainView: View {
.onAppear {
viewModel.send(.onAppear)
}
.sheet(isPresented: $isAddMode) {
AddLinkView()
.sheet(item: .init(
get: { viewModel.state.isPresentedSheet },
set: { viewModel.send(.presentSheet($0))
}
)) { type in
buildSheet(type)
}
}

Expand Down Expand Up @@ -79,7 +82,6 @@ struct MainView: View {
Spacer()
Button {
// TODO: 구현 예정
isAddMode = true
} label: {
Image(systemName: "plus")
}
Expand All @@ -99,6 +101,18 @@ struct MainView: View {
}
}
}

@ViewBuilder
private func buildSheet(_ type: MainViewModel.SheetType) -> some View {
switch type {
case .addLink:
AddLinkView()
case .addFolder:
AddFolderView() {
viewModel.send(.didCreateFolder($0))
}
}
}
}

#Preview {
Expand Down
33 changes: 27 additions & 6 deletions Mark-In/Sources/Feature/Main/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ final class MainViewModel: Reducer {
struct State {
var isLoading: Bool = true

var tabs: [SidebarTab] = [.total, .pin, .nonRead]
var defaultTabs: [SidebarTab] = [.total, .pin, .nonRead]
var folderTabs: [SidebarTab] = []
var selectedTab: SidebarTab? = .total

var isPresentedSheet: SheetType?
}

enum Action {
case onAppear
case refresh
case changeTab(SidebarTab?)

case presentSheet(SheetType?)

case didCreateFolder(Folder)
}

private(set) var state: State = .init()
Expand All @@ -38,13 +45,24 @@ final class MainViewModel: Reducer {
}

case .refresh:
(1...3).forEach { state.tabs.append(.folder(.init(id: "\($0)", name: "\($0)"))) }
// TODO: 실제 데이터 가져오는 작업 구현 필요
(1...3).forEach {
state.folderTabs.append(.folder(.init(id: "\($0)", name: "\($0)", createdBy: .now)))
}
state.isLoading = false
return .none

case .changeTab(let tab):
state.selectedTab = tab
return .none

case .presentSheet(let sheetType):
state.isPresentedSheet = sheetType
return .none

case .didCreateFolder(let folder):
state.folderTabs.append(.folder(folder))
return .none
}
}

Expand All @@ -61,8 +79,11 @@ final class MainViewModel: Reducer {
}
}

// TODO: 이후 제거 예정
struct TestFolder: Hashable {
var id: String
var name: String
extension MainViewModel {
enum SheetType: Identifiable {
case addLink
case addFolder

var id: String { String(describing: self) }
}
}
Loading
Loading