Skip to content

Commit bdcf41d

Browse files
authored
[Merge] #202 - Sprint4 작업항목 플로우 구현, 인스타그램 스토리 공유
2 parents b48f4fc + 199e20a commit bdcf41d

31 files changed

Lines changed: 1472 additions & 396 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// PhotoSelectionPurpose.swift
3+
// Neki-iOS
4+
//
5+
// Created by OneTen on 4/13/26.
6+
//
7+
8+
import Foundation
9+
10+
public enum PhotoSelectionPurpose {
11+
case duplicate
12+
case move
13+
}

Neki-iOS/Features/Archive/Sources/Presentation/Sources/Components/ArchiveImageFooter.swift

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,140 @@
77

88
import SwiftUI
99

10+
public enum ArchiveFooterStyle {
11+
case detail // 사진 상세 화면 (즐겨찾기, 메모 아이콘)
12+
case selection // 사진 선택 화면 (다운로드, 복제, 이동, 삭제 + 텍스트)
13+
}
14+
1015
struct ArchiveImageFooter: View {
1116

1217
// MARK: - Properties
1318

14-
/// 버튼 활성화 여부 (선택 모드에서는 선택된 아이템 유무, 상세 모드에서는 항상 true)
15-
let isEnabled: Bool
19+
let style: ArchiveFooterStyle
1620

17-
/// 즐겨찾기 상태 (nil이면 버튼 숨김)
21+
/// 버튼 활성화 여부
22+
let isEnabled: Bool
23+
/// 즐겨찾기 상태 (상세 모드 전용)
1824
let isFavorite: Bool?
1925

26+
// 아이콘 액션
2027
let onDownload: () -> Void
2128
let onDelete: () -> Void
2229
let onFavorite: (() -> Void)?
2330
let onTapMemo: (() -> Void)?
31+
let onDuplicate: (() -> Void)?
32+
let onMove: (() -> Void)?
2433

2534
// MARK: - Init
2635

2736
public init(
37+
style: ArchiveFooterStyle = .detail,
2838
isEnabled: Bool = true,
2939
isFavorite: Bool? = nil,
3040
onDownload: @escaping () -> Void,
3141
onDelete: @escaping () -> Void,
3242
onFavorite: (() -> Void)? = nil,
33-
onTapMemo: (() -> Void)? = nil
43+
onTapMemo: (() -> Void)? = nil,
44+
onDuplicate: (() -> Void)? = nil,
45+
onMove: (() -> Void)? = nil
3446
) {
47+
self.style = style
3548
self.isEnabled = isEnabled
3649
self.isFavorite = isFavorite
3750
self.onDownload = onDownload
3851
self.onDelete = onDelete
3952
self.onFavorite = onFavorite
4053
self.onTapMemo = onTapMemo
54+
self.onDuplicate = onDuplicate
55+
self.onMove = onMove
4156
}
4257

4358
// MARK: - Body
4459

4560
var body: some View {
61+
if style == .selection {
62+
selectionModeFooter
63+
} else {
64+
detailModeFooter
65+
}
66+
}
67+
}
68+
69+
extension ArchiveImageFooter {
70+
private var selectionModeFooter: some View {
71+
HStack(alignment: .center, spacing: 0) {
72+
selectionButton(
73+
title: "다운로드",
74+
icon: Image(isEnabled ? .iconDownloadFill : .iconDownload),
75+
action: onDownload
76+
)
77+
78+
if let onDuplicate = onDuplicate {
79+
selectionButton(
80+
title: "사진 복제",
81+
icon: Image(isEnabled ? .iconDuplicateFill : .iconDuplicate),
82+
action: onDuplicate
83+
)
84+
}
85+
86+
if let onMove = onMove {
87+
selectionButton(
88+
title: "사진 이동",
89+
icon: Image(isEnabled ? .iconMoveFill : .iconMove),
90+
action: onMove
91+
)
92+
}
93+
94+
selectionButton(
95+
title: "삭제",
96+
icon: Image(isEnabled ? .iconTrashFill : .iconTrash),
97+
action: onDelete
98+
)
99+
}
100+
.padding(.top, 8)
101+
.padding(.bottom, 10)
102+
.background(.white)
103+
.overlay(
104+
Rectangle()
105+
.frame(height: 1)
106+
.foregroundColor(.gray75),
107+
alignment: .top
108+
)
109+
}
110+
111+
// 버튼 공통 뷰 빌더
112+
@ViewBuilder
113+
private func selectionButton(title: String, icon: Image, action: @escaping () -> Void) -> some View {
114+
Button(action: action) {
115+
VStack(spacing: 4) {
116+
icon
117+
.resizable()
118+
.aspectRatio(contentMode: .fit)
119+
.frame(width: 28, height: 28)
120+
.foregroundStyle(isEnabled ? .gray700 : .gray200)
121+
122+
Text(title)
123+
.nekiFont(.body14Medium)
124+
.foregroundStyle(isEnabled ? .gray700 : .gray400)
125+
126+
}
127+
.frame(maxWidth: .infinity)
128+
.contentShape(Rectangle())
129+
}
130+
.disabled(!isEnabled)
131+
}
132+
133+
private var detailModeFooter: some View {
46134
HStack(alignment: .center, spacing: 0) {
47135
Button(action: onDownload) {
48136
Image(isEnabled ? .iconDownloadFill : .iconDownload)
49-
.renderingMode(.template)
50-
.foregroundStyle(isEnabled ? .gray700 : .gray100)
137+
.foregroundStyle(isEnabled ? .gray700 : .gray200)
51138
}
52139
.disabled(!isEnabled)
53140

54141
if let isFavorite = isFavorite, let onFavorite = onFavorite {
55142
Button(action: onFavorite) {
56143
Image(isFavorite ? .iconHeart28Fill : .iconHeart28Gray)
57-
.renderingMode(.template)
58-
.foregroundStyle(isFavorite ? .red : .gray700)
59144
}
60145
.padding(.leading, 16)
61146
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// Date+.swift
3+
// Neki-iOS
4+
//
5+
// Created by OneTen on 4/2/26.
6+
//
7+
8+
import Foundation
9+
10+
extension Date {
11+
/// Date를 "yyyy.MM.dd" 형태의 문자열로 변환합니다.
12+
func toDotFormatString() -> String {
13+
return DateFormatters.dotFormat.string(from: self)
14+
}
15+
}

Neki-iOS/Features/Archive/Sources/Presentation/Sources/Extension/String+.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ extension String {
3535
}
3636
}
3737

38-
private struct DateFormatters {
38+
struct DateFormatters {
3939

4040
// 1. 밀리초 + 타임존 (Standard InternetDateTime + Fractional)
4141
static let iso8601Fractional: ISO8601DateFormatter = {
@@ -71,4 +71,11 @@ private struct DateFormatters {
7171
]
7272
return formatter
7373
}()
74+
75+
static let dotFormat: DateFormatter = {
76+
let formatter = DateFormatter()
77+
formatter.dateFormat = "yyyy.MM.dd"
78+
formatter.locale = Locale(identifier: "ko_KR")
79+
return formatter
80+
}()
7481
}

0 commit comments

Comments
 (0)