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
21 changes: 21 additions & 0 deletions Mark-In/Sources/Feature/Common/Reducer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Reducer.swift
// Mark-In
//
// Created by 이정동 on 4/25/25.
//

import Foundation

protocol Reducer {
associatedtype State
associatedtype Action

func send(_ action: Action)
func reduce(state: inout State, action: Action) -> Effect<Action>
}

enum Effect<Action> {
case none
case run(() async -> Action)
}
5 changes: 4 additions & 1 deletion Mark-In/Sources/Feature/Main/LinkListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI

import DesignSystem

private enum ViewConstants {
Expand All @@ -15,6 +16,8 @@ private enum ViewConstants {
}

struct LinkListView: View {

let viewModel: MainViewModel

var body: some View {
GeometryReader { geometry in
Expand Down Expand Up @@ -91,6 +94,6 @@ private struct LinkCell: View {
}

#Preview {
LinkListView()
LinkListView(viewModel: MainViewModel())
.frame(width: 600, height: 600)
}
43 changes: 27 additions & 16 deletions Mark-In/Sources/Feature/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,40 @@ import SwiftUI

import DesignSystem


struct MainView: View {
@State private var selectedIndex: Int? = 1
@State private var viewModel = MainViewModel()
@State private var searchText: String = ""
@State private var isAddMode: Bool = false

var body: some View {

NavigationSplitView {
SideBar(selectedIndex: $selectedIndex)
.navigationSplitViewColumnWidth(
min: 200, ideal: 200, max: 300
)
} detail: {
LinkListView()
}
.navigationTitle("")
.searchable(text: $searchText, placement: .toolbar)
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
toolBarButtons
ZStack {
NavigationSplitView {
SideBar(viewModel: viewModel)
.navigationSplitViewColumnWidth(
min: 200, ideal: 200, max: 300
)
} detail: {
LinkListView(viewModel: viewModel)
}
.navigationTitle("")
.searchable(text: $searchText, placement: .toolbar)
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
toolBarButtons
}
}
.disabled(viewModel.state.isLoading)

if viewModel.state.isLoading {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.background(.gray.opacity(0.5))
}
}
.onAppear {
viewModel.send(.onAppear)
}
.sheet(isPresented: $isAddMode) {
AddLinkView()
}
Expand Down
94 changes: 94 additions & 0 deletions Mark-In/Sources/Feature/Main/MainViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// MainViewModel.swift
// Mark-In
//
// Created by 이정동 on 4/25/25.
//

import Foundation

@Observable
final class MainViewModel: Reducer {
struct State {
var isLoading: Bool = true

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

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

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

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 .onAppear:
return .run {
try? await Task.sleep(nanoseconds: 3_000_000_000)
return .refresh
}

case .refresh:
(1...3).forEach { state.tabs.append(.folder(.init(id: "\($0)", name: "\($0)"))) }
state.isLoading = false
return .none

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

private func handleEffect(_ effect: Effect<Action>) {
switch effect {
case .none: break
case .run(let action):
Task { send(await action()) }
}
}
}

enum SidebarTab: Hashable {
case total
case pin
case nonRead
case folder(TestFolder)

var title: String {
switch self {
case .total: "전체"
case .pin: "즐겨찾기"
case .nonRead: "읽지 않음"
case .folder(let folder): folder.name
}
}

var icon: String {
switch self {
case .total: "clock"
case .pin: "star"
case .nonRead: "xmark.circle"
case .folder(_): "folder"
}
}

var isFolder: Bool {
if case .folder(_) = self { true }
else { false }
}
}

// TODO: 이후 제거 예정
struct TestFolder: Hashable {
var id: String
var name: String
}
43 changes: 21 additions & 22 deletions Mark-In/Sources/Feature/Main/SideBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,38 @@
//

import SwiftUI

import DesignSystem

struct SideBar: View {
@Binding var selectedIndex: Int?
let viewModel: MainViewModel

var body: some View {
VStack(alignment: .leading) {

List(selection: $selectedIndex) {
List(selection: .init(
get: { viewModel.state.selectedTab },
set: { viewModel.send(.changeTab($0)) })
) {
Section("기본") {
NavigationLink(value: 1) {
Label("전체", systemImage: "clock")
}

NavigationLink(value: 2) {
Label("즐겨찾기", systemImage: "star")
}

NavigationLink(value: 3) {
Label("읽지 않음", systemImage: "xmark.circle")
ForEach(
viewModel.state.tabs.filter { !$0.isFolder },
id: \.self
) { tab in
NavigationLink(value: tab) {
Label(tab.title, systemImage: tab.icon)
}
}
}

Section("저장된 폴더") {
NavigationLink(value: 4) {
Label("폴더1", systemImage: "folder")
}
NavigationLink(value: 5) {
Label("폴더2", systemImage: "folder")
}
NavigationLink(value: 6) {
Label("폴더3", systemImage: "folder")
ForEach(
viewModel.state.tabs.filter { $0.isFolder },
id: \.self
) { tab in
NavigationLink(value: tab) {
Label(tab.title, systemImage: tab.icon)
}
}
}
}
Expand All @@ -63,6 +63,5 @@ struct SideBar: View {
}

#Preview {
@Previewable @State var selectedIndex: Int? = 1
SideBar(selectedIndex: $selectedIndex)
SideBar(viewModel: MainViewModel())
}
Loading