Skip to content

Commit b4a17ce

Browse files
committed
#3 [Feat] : Magazine 데이터 업데이트 기능완료
1 parent 4ff0348 commit b4a17ce

File tree

3 files changed

+203
-89
lines changed

3 files changed

+203
-89
lines changed

ToucheAdmin/View/Magazine/Content/MagazineContentView.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@ struct MagazineContentView: View {
1717
HStack(alignment: .top, spacing: 8.0) {
1818
// Content Image...
1919
// ===================== FireStorage Issue ===================
20-
// AsyncImage(
21-
// url: URL(string: magazine.contentImage)) { image in
22-
// image
23-
// .resizable()
24-
// .scaledToFit()
25-
// .frame(width: 50, height: 50)
26-
// .cornerRadius(6)
27-
//
28-
// } placeholder: {
29-
// ProgressView()
30-
// }
20+
AsyncImage(
21+
url: URL(string: magazine.contentImage)) { image in
22+
image
23+
.resizable()
24+
.scaledToFit()
25+
.frame(width: 50, height: 50)
26+
.cornerRadius(6)
27+
28+
} placeholder: {
29+
ProgressView()
30+
}
3131
// ========================== comment below ===================
32-
Rectangle()
33-
.frame(width: 50, height: 50)
32+
// Rectangle()
33+
// .frame(width: 50, height: 50)
3434
// ============================================================
3535

3636
VStack(alignment: .leading, spacing: 2) {

ToucheAdmin/View/Magazine/Detail/MagazineEditView.swift

Lines changed: 159 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,66 @@
66
//
77

88
import SwiftUI
9+
import Combine
10+
11+
final class MagazineEditViewModel: ObservableObject {
12+
@Published var title: String = ""
13+
@Published var subTitle: String = ""
14+
@Published var contentImage: NSImage?
15+
@Published var bodyImage: NSImage?
16+
@Published var isLoading: Bool = false
17+
// assigning properties
18+
@Published var canSaveState: Bool = false
19+
var cancellables = Set<AnyCancellable>()
20+
21+
enum ImageCategory {
22+
case content
23+
case body
24+
}
25+
26+
init() {
27+
$title
28+
.combineLatest($subTitle, $contentImage, $bodyImage)
29+
.map { title, subTitle, contentImage, bodyImage in
30+
return !title.isEmpty && !subTitle.isEmpty && contentImage != nil && bodyImage != nil
31+
}
32+
.assign(to: &$canSaveState)
33+
}
34+
35+
func fecthNSImage(url: URL, category: ImageCategory) {
36+
URLSession.shared.dataTaskPublisher(for: url)
37+
.receive(on: DispatchQueue.main)
38+
.compactMap { (data: Data, response: URLResponse) -> NSImage? in
39+
guard let response = response as? HTTPURLResponse,
40+
(200..<300).contains(response.statusCode),
41+
let nsImage = NSImage(data: data) else { return nil }
42+
return nsImage
43+
}
44+
.sink { status in
45+
switch status {
46+
case .finished:
47+
print("fetch done")
48+
case .failure(let error):
49+
print(error)
50+
}
51+
} receiveValue: { [weak self] nsImage in
52+
switch category {
53+
case .content:
54+
self?.contentImage = nsImage
55+
case .body:
56+
self?.bodyImage = nsImage
57+
}
58+
}
59+
.store(in: &cancellables)
60+
61+
}
62+
}
963

1064
// openPannel: [https://serialcoder.dev/text-tutorials/macos-tutorials/save-and-open-panels-in-swiftui-based-macos-apps/]
1165
// drag and drop image: [https://www.hackingwithswift.com/quick-start/swiftui/how-to-support-drag-and-drop-in-swiftui]
1266
struct MagazineEditView: View {
1367
@Binding var flow: Flow
14-
@State private var title: String = ""
15-
@State private var subTitle: String = ""
16-
@State private var contentImage: NSImage?
17-
@State private var bodyImage: NSImage?
68+
@StateObject var vm = MagazineEditViewModel()
1869
@EnvironmentObject var perfumeStore: PerfumeStore
1970
@EnvironmentObject var magazineStore: MagazineStore
2071
var perfumes: [Perfume] {
@@ -33,42 +84,52 @@ struct MagazineEditView: View {
3384
.font(.footnote)
3485
.foregroundColor(.secondary)
3586

36-
HStack {
37-
Spacer()
38-
Button {
39-
// 모든 조건이 부합해야 저장기능 가능
40-
guard !title.isEmpty && !subTitle.isEmpty,
41-
let contentImage = contentImage,
42-
let bodyImage = bodyImage else { return }
43-
44-
// 새로운 메거진 생성
45-
let magazine = Magazine(
46-
title: title,
47-
subTitle: subTitle,
48-
contentImage: "",
49-
bodyImage: "",
50-
createdDate: Date.now.timeIntervalSince1970,
51-
perfumeIds: perfumes.map { $0.perfumeId }
52-
)
53-
54-
Task {
55-
// 메거진 서버에 저장
56-
await magazineStore.createMagazine(magazine: magazine, selectedContentUImage: contentImage, selectedBodyUImage: bodyImage)
57-
// 읽기 모드
58-
flow = .read
87+
if magazineStore.magazine == nil {
88+
HStack {
89+
Spacer()
90+
Button {
91+
flow = .create
92+
} label: {
93+
Label("back", systemImage: "chevron.left")
5994
}
60-
} label: {
61-
Text("Save")
62-
}
63-
} // HSTACK(SAVE)
95+
Button {
96+
// 새로운 메거진 생성
97+
let magazine = Magazine(
98+
title: vm.title,
99+
subTitle: vm.subTitle,
100+
contentImage: "",
101+
bodyImage: "",
102+
createdDate: Date.now.timeIntervalSince1970,
103+
perfumeIds: perfumes.map { $0.perfumeId }
104+
)
105+
106+
107+
108+
Task {
109+
// upload start
110+
vm.isLoading = true
111+
// 메거진 서버에 저장
112+
await magazineStore.createMagazine(magazine: magazine, selectedContentUImage: vm.contentImage, selectedBodyUImage: vm.bodyImage)
113+
// 읽기 모드
114+
flow = .read
115+
// upload end
116+
vm.isLoading = false
117+
}
118+
119+
} label: {
120+
Text("Save")
121+
}
122+
.disabled(!vm.canSaveState)
123+
} // HSTACK(SAVE)
124+
}
64125

65126
Form {
66-
TextField(text: $title, prompt: Text("Required.."), axis: .vertical) {
127+
TextField(text: $vm.title, prompt: Text("Required.."), axis: .vertical) {
67128
Text("Title")
68129
}
69130
.textFieldStyle(RoundedBorderTextFieldStyle())
70131

71-
TextField(text: $subTitle, prompt: Text("Required.."), axis: .vertical) {
132+
TextField(text: $vm.subTitle, prompt: Text("Required.."), axis: .vertical) {
72133
Text("Sub Title")
73134
}
74135
.textFieldStyle(RoundedBorderTextFieldStyle())
@@ -109,10 +170,10 @@ struct MagazineEditView: View {
109170
Button {
110171
if let url = showOpenPanel(),
111172
let nsImage = NSImage(contentsOf: url) {
112-
contentImage = nsImage
173+
vm.contentImage = nsImage
113174
}
114175
} label: {
115-
if let contentImage = contentImage {
176+
if let contentImage = vm.contentImage {
116177
Image(nsImage: contentImage)
117178
.resizable()
118179
.frame(width: 250, height: 250, alignment: .center)
@@ -122,7 +183,7 @@ struct MagazineEditView: View {
122183
.fill(.quaternary)
123184
.frame(width: 250, height: 250)
124185
.overlay {
125-
Text("Select the image!\n")
186+
Text("Select the image!\n**+**")
126187
.multilineTextAlignment(.center)
127188
}
128189

@@ -131,7 +192,7 @@ struct MagazineEditView: View {
131192
.buttonStyle(.plain)
132193
.dropDestination(for: Data.self) { items, location in
133194
if let item = items.first {
134-
contentImage = NSImage(data: item)
195+
vm.contentImage = NSImage(data: item)
135196
}
136197

137198
return true
@@ -147,10 +208,10 @@ struct MagazineEditView: View {
147208
Button {
148209
if let url = showOpenPanel(),
149210
let nsImage = NSImage(contentsOf: url) {
150-
bodyImage = nsImage
211+
vm.bodyImage = nsImage
151212
}
152213
} label: {
153-
if let bodyImage = bodyImage {
214+
if let bodyImage = vm.bodyImage {
154215
Image(nsImage: bodyImage)
155216
.resizable()
156217
.frame(width: 250, height: 250, alignment: .center)
@@ -160,15 +221,15 @@ struct MagazineEditView: View {
160221
.fill(.quaternary)
161222
.frame(width: 250, height: 250)
162223
.overlay {
163-
Text("Select the image!\n")
224+
Text("Select the image!\n**+**")
164225
.multilineTextAlignment(.center)
165226
}
166227
}
167228
}
168229
.buttonStyle(.plain)
169230
.dropDestination(for: Data.self) { items, location in
170231
if let item = items.first {
171-
bodyImage = NSImage(data: item)
232+
vm.bodyImage = NSImage(data: item)
172233
}
173234

174235
return true
@@ -182,14 +243,65 @@ struct MagazineEditView: View {
182243
} // VSTACK
183244
.padding()
184245
} // SCROLL
246+
.onAppear {
247+
switch flow {
248+
case .edit:
249+
if let magazine = magazineStore.magazine,
250+
let contentImageURL = URL(string: magazine.contentImage),
251+
let bodyImageURL = URL(string: magazine.bodyImage) {
252+
vm.title = magazine.title
253+
vm.subTitle = magazine.subTitle
254+
vm.fecthNSImage(url: contentImageURL, category: .content)
255+
vm.fecthNSImage(url: bodyImageURL, category: .body)
256+
// images
257+
// url -> download -> NSImage
258+
}
259+
default:
260+
vm.title = ""
261+
vm.subTitle = ""
262+
vm.contentImage = nil
263+
vm.bodyImage = nil
264+
}
265+
}
266+
.overlay(content: {
267+
if vm.isLoading {
268+
ProgressView()
269+
.frame(width: 200, height: 200)
270+
.background(Material.ultraThinMaterial)
271+
.clipShape(RoundedRectangle(cornerRadius: 20.0))
272+
}
273+
})
274+
.onChange(of: magazineStore.magazine) { magazine in
275+
flow = .read
276+
}
185277
.toolbar {
186-
ToolbarItem {
187-
Button {
188-
flow = .create
189-
} label: {
190-
Image(systemName: "chevron.left")
278+
if let magazine = magazineStore.magazine {
279+
ToolbarItem(placement: ToolbarItemPlacement.automatic) {
280+
Button("done") {
281+
flow = .read
282+
// 기존 메거진 업데이트
283+
let magazine = Magazine(
284+
id: magazine.id,
285+
title: vm.title,
286+
subTitle: vm.subTitle,
287+
contentImage: "",
288+
bodyImage: "",
289+
createdDate: Date.now.timeIntervalSince1970,
290+
perfumeIds: perfumes.map { $0.perfumeId }
291+
)
292+
293+
Task {
294+
// upload start
295+
vm.isLoading = true
296+
// 메거진 서버에 저장
297+
await magazineStore.createMagazine(magazine: magazine, selectedContentUImage: vm.contentImage, selectedBodyUImage: vm.bodyImage)
298+
// 읽기 모드
299+
flow = .read
300+
// upload end
301+
vm.isLoading = false
302+
}
303+
}
191304
}
192-
193305
}
194306
}
195307
}

ToucheAdmin/View/Magazine/Detail/MagazineReadView.swift

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,43 +68,45 @@ struct MagazineReadView: View {
6868
.foregroundColor(.secondary)
6969

7070
// ======================== FireStorage Issue ==============
71-
// AsyncImage(
72-
// url: URL(string: magazine.contentImage),
73-
// content: { image in
74-
// image
75-
// .resizable()
76-
// .aspectRatio(1.0, contentMode: .fill)
77-
// .frame(width: 250, height: 250)
78-
// .cornerRadius(8.0)
79-
// }) {
80-
// ProgressView()
81-
// .frame(width: 250, height: 250)
82-
// }
71+
AsyncImage(
72+
url: URL(string: magazine.contentImage),
73+
content: { image in
74+
image
75+
.resizable()
76+
.aspectRatio(1.0, contentMode: .fill)
77+
.frame(width: 250, height: 250)
78+
.cornerRadius(8.0)
79+
}) {
80+
ProgressView()
81+
.frame(width: 250, height: 250)
82+
}
8383
// ================== comment below =============================
84-
Rectangle()
85-
.frame(width: 250, height: 250)
84+
// Rectangle()
85+
// .frame(width: 250, height: 250)
8686
// ==============================================================
8787
} // VSTACK(CONTENT IMAGE)
8888

8989
VStack(alignment: .leading, spacing: 4) {
9090
Text("Body Image")
9191
.font(.caption)
9292
.foregroundColor(.secondary)
93-
94-
// AsyncImage(
95-
// url: URL(string: magazine.bodyImage),
96-
// content: { image in
97-
// image
98-
// .resizable()
99-
// .aspectRatio(1.0, contentMode: .fill)
100-
// .frame(width: 250, height: 250)
101-
// .cornerRadius(8.0)
102-
// }) {
103-
// ProgressView()
104-
// .frame(width: 250, height: 250)
105-
// }
106-
Rectangle()
107-
.frame(width: 250, height: 250)
93+
// ======================== FireStorage Issue ==============
94+
AsyncImage(
95+
url: URL(string: magazine.bodyImage),
96+
content: { image in
97+
image
98+
.resizable()
99+
.aspectRatio(1.0, contentMode: .fill)
100+
.frame(width: 250, height: 250)
101+
.cornerRadius(8.0)
102+
}) {
103+
ProgressView()
104+
.frame(width: 250, height: 250)
105+
}
106+
// ================== comment below =============================
107+
// Rectangle()
108+
// .frame(width: 250, height: 250)
109+
// ==============================================================
108110
} // VSTACK(BODY IMAGE)
109111

110112
Spacer()

0 commit comments

Comments
 (0)