Skip to content

Commit 8ad2e54

Browse files
authored
Merge pull request #12 from nativeapptemplate/add_other_viewModels2
add ItemTagDetailViewModel
2 parents a5f7de7 + 087b65f commit 8ad2e54

File tree

14 files changed

+1750
-384
lines changed

14 files changed

+1750
-384
lines changed

NativeAppTemplate.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@
136136
01D85A962E07C78400A95798 /* NumberTagsWebpageListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A952E07C78400A95798 /* NumberTagsWebpageListViewModel.swift */; };
137137
01D85A9A2E07C85900A95798 /* ShopBasicSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A992E07C85900A95798 /* ShopBasicSettingsViewModel.swift */; };
138138
01D85A9E2E07C9BD00A95798 /* ShopSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A9D2E07C9BD00A95798 /* ShopSettingsViewModel.swift */; };
139+
01D85AE72E07CD4400A95798 /* ItemTagDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AE62E07CD4400A95798 /* ItemTagDetailViewModel.swift */; };
140+
01D85AEB2E07CF3600A95798 /* ItemTagEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AEA2E07CF3600A95798 /* ItemTagEditViewModel.swift */; };
141+
01D85AEF2E07D20500A95798 /* ItemTagCreateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AEE2E07D20500A95798 /* ItemTagCreateViewModel.swift */; };
142+
01D85AF32E07D37E00A95798 /* ItemTagListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AF22E07D37E00A95798 /* ItemTagListViewModel.swift */; };
139143
01D8AE8B2AB453C1009AFFBA /* ShopBasicSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */; };
140144
01DCE23F298FA3B300BA311D /* ShopListCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DCE23E298FA3B300BA311D /* ShopListCardView.swift */; };
141145
01E0A59C25BD088600298D35 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E0A59125BD087E00298D35 /* SettingsView.swift */; };
@@ -296,6 +300,10 @@
296300
01D85A952E07C78400A95798 /* NumberTagsWebpageListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberTagsWebpageListViewModel.swift; sourceTree = "<group>"; };
297301
01D85A992E07C85900A95798 /* ShopBasicSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopBasicSettingsViewModel.swift; sourceTree = "<group>"; };
298302
01D85A9D2E07C9BD00A95798 /* ShopSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopSettingsViewModel.swift; sourceTree = "<group>"; };
303+
01D85AE62E07CD4400A95798 /* ItemTagDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagDetailViewModel.swift; sourceTree = "<group>"; };
304+
01D85AEA2E07CF3600A95798 /* ItemTagEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagEditViewModel.swift; sourceTree = "<group>"; };
305+
01D85AEE2E07D20500A95798 /* ItemTagCreateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagCreateViewModel.swift; sourceTree = "<group>"; };
306+
01D85AF22E07D37E00A95798 /* ItemTagListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagListViewModel.swift; sourceTree = "<group>"; };
299307
01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopBasicSettingsView.swift; sourceTree = "<group>"; };
300308
01DCE23E298FA3B300BA311D /* ShopListCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopListCardView.swift; sourceTree = "<group>"; };
301309
01E0A59125BD087E00298D35 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
@@ -724,7 +732,9 @@
724732
isa = PBXGroup;
725733
children = (
726734
017278932D7D99D100CE424F /* ItemTagDetailView.swift */,
735+
01D85AE62E07CD4400A95798 /* ItemTagDetailViewModel.swift */,
727736
017278942D7D99D100CE424F /* ItemTagEditView.swift */,
737+
01D85AEA2E07CF3600A95798 /* ItemTagEditViewModel.swift */,
728738
);
729739
path = "ItemTag Detail";
730740
sourceTree = "<group>";
@@ -733,8 +743,10 @@
733743
isa = PBXGroup;
734744
children = (
735745
017278962D7D99D100CE424F /* ItemTagCreateView.swift */,
746+
01D85AEE2E07D20500A95798 /* ItemTagCreateViewModel.swift */,
736747
017278972D7D99D100CE424F /* ItemTagListCardView.swift */,
737748
017278982D7D99D100CE424F /* ItemTagListView.swift */,
749+
01D85AF22E07D37E00A95798 /* ItemTagListViewModel.swift */,
738750
);
739751
path = "ItemTag List";
740752
sourceTree = "<group>";
@@ -932,6 +944,7 @@
932944
"",
933945
"",
934946
"",
947+
"",
935948
);
936949
};
937950
/* End PBXShellScriptBuildPhase section */
@@ -962,6 +975,7 @@
962975
01FC03E22B3329B700E6CD8E /* NeedAppUpdatesView.swift in Sources */,
963976
0172033725A9642E008FD63B /* JSONAPIResource.swift in Sources */,
964977
01B37C7629B0960700BF5B2D /* ForgotPasswordView.swift in Sources */,
978+
01D85AEF2E07D20500A95798 /* ItemTagCreateViewModel.swift in Sources */,
965979
01E0A5B725BD0FCD00298D35 /* OfflineView.swift in Sources */,
966980
0110A15F2AC816F5003EDCBA /* SendConfirmation.swift in Sources */,
967981
0199CD2A2E07512100109DC6 /* OnboardingRepositoryProtocol.swift in Sources */,
@@ -1039,6 +1053,7 @@
10391053
01482FA42B351E4100A56D43 /* AcceptPrivacyView.swift in Sources */,
10401054
0172046325AA82BF008FD63B /* OnboardingView.swift in Sources */,
10411055
013DE735284E99DF00528CC5 /* ShopCreateView.swift in Sources */,
1056+
01D85AE72E07CD4400A95798 /* ItemTagDetailViewModel.swift in Sources */,
10421057
017203B625A96FD6008FD63B /* View+Extensions.swift in Sources */,
10431058
0106414229A9F51700B46FED /* AccountPasswordRepository.swift in Sources */,
10441059
0172033A25A9642E008FD63B /* JSONAPIDocument.swift in Sources */,
@@ -1061,6 +1076,7 @@
10611076
0172789A2D7D99D100CE424F /* ItemTagListCardView.swift in Sources */,
10621077
0172789B2D7D99D100CE424F /* ItemTagListView.swift in Sources */,
10631078
0172789C2D7D99D100CE424F /* ItemTagDetailView.swift in Sources */,
1079+
01D85AEB2E07CF3600A95798 /* ItemTagEditViewModel.swift in Sources */,
10641080
0172789D2D7D99D100CE424F /* ItemTagCreateView.swift in Sources */,
10651081
0172789E2D7D99D100CE424F /* ItemTagEditView.swift in Sources */,
10661082
0172786F2D7D87D000CE424F /* String+Extensions.swift in Sources */,
@@ -1069,6 +1085,7 @@
10691085
01E0A59C25BD088600298D35 /* SettingsView.swift in Sources */,
10701086
0172052525AAFA43008FD63B /* Shopkeeper.swift in Sources */,
10711087
017278752D7D8FAC00CE424F /* ItemTagRepository.swift in Sources */,
1088+
01D85AF32E07D37E00A95798 /* ItemTagListViewModel.swift in Sources */,
10721089
0172047E25AA8343008FD63B /* Font+Extensions.swift in Sources */,
10731090
0172034825A9642E008FD63B /* Request.swift in Sources */,
10741091
0172048025AA8343008FD63B /* UIColor+Extensions.swift in Sources */,

NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagDetailView.swift

Lines changed: 40 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,21 @@ import CoreNFC
1111

1212
struct ItemTagDetailView: View {
1313
@Environment(\.dismiss) private var dismiss
14-
@Environment(MessageBus.self) private var messageBus
15-
@Environment(\.sessionController) private var sessionController
16-
private var itemTagRepository: ItemTagRepositoryProtocol
17-
@StateObject private var nfcManager = appSingletons.nfcManager
18-
@State private var isLocked = false
19-
@State private var isShowingEditSheet = false
20-
@State private var isShowingDeleteConfirmationDialog = false
21-
@State private var isFetching = true
22-
@State private var isGeneratingQrCode = false
23-
@State private var isDeleting = false
24-
@State private var customerTagQrCodeImage: UIImage?
25-
private let qrCodeGenerator = QRCodeGenerator()
26-
private let imageSaver = ImageSaver()
14+
@State private var viewModel: ItemTagDetailViewModel
2715

28-
private var shop: Shop
29-
private var itemTagId: String
30-
31-
private var itemTag: Binding<ItemTag> {
32-
Binding {
33-
itemTagRepository.findBy(id: itemTagId)
34-
} set: { _ in
35-
}
36-
}
37-
38-
init(
39-
itemTagRepository: ItemTagRepositoryProtocol,
40-
shop: Shop,
41-
itemTagId: String
42-
) {
43-
self.itemTagRepository = itemTagRepository
44-
self.shop = shop
45-
self.itemTagId = itemTagId
16+
init(viewModel: ItemTagDetailViewModel) {
17+
self._viewModel = State(wrappedValue: viewModel)
4618
}
4719

4820
var body: some View {
4921
contentView
5022
.task {
51-
reload()
23+
viewModel.reload()
24+
}
25+
.onChange(of: viewModel.shouldDismiss) { _, shouldDismiss in
26+
if shouldDismiss {
27+
dismiss()
28+
}
5229
}
5330
}
5431
}
@@ -58,7 +35,7 @@ private extension ItemTagDetailView {
5835
var contentView: some View {
5936

6037
@ViewBuilder var contentView: some View {
61-
if isFetching || isDeleting || isGeneratingQrCode {
38+
if viewModel.isBusy {
6239
LoadingView()
6340
} else {
6441
itemTagDetailView
@@ -76,26 +53,28 @@ private extension ItemTagDetailView {
7653
.font(.title2)
7754
.padding(.top, 8)
7855

79-
Text(shop.name)
56+
Text(viewModel.shop.name)
8057
.font(.title3)
8158
.padding(.top, 16)
8259

83-
Text(String(itemTag.wrappedValue.queueNumber))
84-
.font(.largeTitle)
85-
.bold()
86-
.padding(.top, 8)
87-
.foregroundStyle(.lightestAccent)
60+
if let itemTag = viewModel.itemTag {
61+
Text(String(itemTag.queueNumber))
62+
.font(.largeTitle)
63+
.bold()
64+
.padding(.top, 8)
65+
.foregroundStyle(.lightestAccent)
66+
}
8867
}
8968

9069
GroupBox(label: Label(String("Lock"), systemImage: "lock") ) {
91-
Toggle(isOn: $isLocked) {
70+
Toggle(isOn: $viewModel.isLocked) {
9271
Text(verbatim: "Lock")
9372
}
9473
.dynamicTypeSize(...DynamicTypeSize.large)
9574
.frame(width: 96)
9675
.tint(.lockForeground)
9776

98-
if isLocked {
77+
if viewModel.isLocked {
9978
Text(String.youCannotUndoAfterLockingTag)
10079
.font(.uiFootnote)
10180
.foregroundStyle(.alarm)
@@ -106,22 +85,7 @@ private extension ItemTagDetailView {
10685

10786
GroupBox(label: Label(String("Server"), systemImage: "storefront") ) {
10887
MainButtonView(title: String.writeServerTag, type: .server(withArrow: false)) {
109-
guard NFCNDEFReaderSession.readingAvailable else {
110-
messageBus.post(
111-
message: Message(
112-
level: .error,
113-
message: String.thisDeviceDoesNotSupportTagScanning,
114-
autoDismiss: false
115-
)
116-
)
117-
return
118-
}
119-
120-
let ndefMessage = createNdefMessage(itemTag: itemTag.wrappedValue, itemTagType: .server)
121-
122-
Task {
123-
await nfcManager.startWriting(ndefMessage: ndefMessage, isLock: isLocked)
124-
}
88+
viewModel.writeServerTag()
12589
}
12690
.padding()
12791
}
@@ -130,48 +94,17 @@ private extension ItemTagDetailView {
13094

13195
GroupBox(label: Label(String("Customer"), systemImage: "person.2") ) {
13296
MainButtonView(title: String.writeCustomerTag, type: .customer(withArrow: false)) {
133-
guard NFCNDEFReaderSession.readingAvailable else {
134-
messageBus.post(
135-
message: Message(
136-
level: .error,
137-
message: String.thisDeviceDoesNotSupportTagScanning,
138-
autoDismiss: false
139-
)
140-
)
141-
return
142-
}
143-
144-
let ndefMessage = createNdefMessage(itemTag: itemTag.wrappedValue, itemTagType: .customer)
145-
146-
Task {
147-
await nfcManager.startWriting(ndefMessage: ndefMessage, isLock: isLocked)
148-
}
97+
viewModel.writeCustomerTag()
14998
}
15099
.padding()
151100

152-
if let customerTagQrCodeImage = customerTagQrCodeImage {
101+
if let customerTagQrCodeImage = viewModel.customerTagQrCodeImage {
153102
Image(uiImage: customerTagQrCodeImage)
154103
.resizable()
155104
.frame(width: 96, height: 96)
156105

157106
Button {
158-
getSaveToPhotoAlbumPermissionIfNeeded { granted in
159-
guard granted else { return }
160-
161-
imageSaver.save(image: customerTagQrCodeImage) { error in
162-
if let error {
163-
messageBus.post(
164-
message: Message(
165-
level: .error,
166-
message: "\(String.customerQrCodeImageSavedToPhotoAlbumError)(\(error))",
167-
autoDismiss: false
168-
)
169-
)
170-
} else {
171-
messageBus.post(message: Message(level: .success, message: .customerQrCodeImageSavedToPhotoAlbum))
172-
}
173-
}
174-
}
107+
viewModel.saveImageToPhotoAlbum()
175108
} label: {
176109
Text(String.saveToPhotoAlbum)
177110
}
@@ -185,123 +118,59 @@ private extension ItemTagDetailView {
185118
}
186119
}
187120
.sheet(
188-
isPresented: $isShowingEditSheet,
121+
isPresented: $viewModel.isShowingEditSheet,
189122
onDismiss: {
190-
reload()
123+
viewModel.reload()
191124
},
192125
content: {
193-
ItemTagEditView(itemTagRepository: itemTagRepository, itemTagId: itemTagId)
126+
ItemTagEditView(
127+
viewModel: ItemTagEditViewModel(
128+
itemTagRepository: viewModel.itemTagRepository,
129+
messageBus: viewModel.messageBus,
130+
sessionController: viewModel.sessionController,
131+
itemTagId: viewModel.itemTagId
132+
)
133+
)
194134
}
195135
)
196136
.confirmationDialog(
197137
String.buttonDeleteTag,
198-
isPresented: $isShowingDeleteConfirmationDialog
138+
isPresented: $viewModel.isShowingDeleteConfirmationDialog
199139
) {
200140
Button(String.buttonDeleteTag, role: .destructive) {
201-
destroyItemTag()
141+
viewModel.destroyItemTag()
202142
}
203143
Button(String.cancel, role: .cancel) {
204-
isShowingDeleteConfirmationDialog = false
144+
viewModel.isShowingDeleteConfirmationDialog = false
205145
}
206146
} message: {
207147
Text(String.areYouSure)
208148
}
209149
.toolbar {
210150
ToolbarItem(placement: .navigationBarTrailing) {
211151
Button {
212-
isShowingEditSheet.toggle()
152+
viewModel.isShowingEditSheet.toggle()
213153
} label: {
214154
Text(String.edit)
215155
}
216156
}
217157
ToolbarItem(placement: .navigationBarTrailing) {
218158
Button {
219-
isShowingDeleteConfirmationDialog.toggle()
159+
viewModel.isShowingDeleteConfirmationDialog.toggle()
220160
} label: {
221161
Image(systemName: "trash")
222162
}
223163
}
224164
}
225165
}
226166

227-
private func reload() {
228-
fetchItemTagDetail()
229-
}
230-
231-
private func reloadCustomerTagQrCodeImage() {
232-
isGeneratingQrCode = true
233-
234-
let scanUrl = itemTag.wrappedValue.scanUrl(itemTagType: ItemTagType.customer)
235-
236-
customerTagQrCodeImage = qrCodeGenerator.generateWithCenterText(
237-
inputText: scanUrl.absoluteString,
238-
centerText: String(itemTag.wrappedValue.queueNumber)
239-
)
240-
241-
isGeneratingQrCode = false
242-
}
243-
244-
private func fetchItemTagDetail() {
245-
Task { @MainActor in
246-
do {
247-
isFetching = true
248-
_ = try await itemTagRepository.fetchDetail(id: itemTagId)
249-
isFetching = false
250-
} catch {
251-
messageBus.post(message: Message(level: .error, message: error.localizedDescription, autoDismiss: false))
252-
dismiss()
253-
}
254-
}
255-
}
256-
257167
private var generateCustomerQrCodeView: some View {
258168
VStack {
259169
Button {
260-
reloadCustomerTagQrCodeImage()
170+
viewModel.generateCustomerQrCode()
261171
} label: {
262172
Text(String.generateCustomerQrCode)
263173
}
264174
}
265175
}
266-
267-
private func destroyItemTag() {
268-
Task { @MainActor in
269-
isDeleting = true
270-
271-
do {
272-
try await itemTagRepository.destroy(id: itemTag.id)
273-
messageBus.post(message: Message(level: .success, message: .itemTagDeleted))
274-
} catch {
275-
messageBus.post(message: Message(level: .error, message: "\(String.itemTagDeletedError) \(error.localizedDescription)", autoDismiss: false))
276-
}
277-
278-
dismiss()
279-
}
280-
}
281-
282-
private func createNdefMessage(itemTag: ItemTag, itemTagType: ItemTagType) -> NFCNDEFMessage {
283-
let scanUrl = itemTag.scanUrl(itemTagType: itemTagType)
284-
let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(url: scanUrl)
285-
let androidAarPayloadData = String.androidAar.data(using: .utf8)!
286-
let androidAarPayload = NFCNDEFPayload(format: .nfcExternal, type: Data(String.androidAarNfcndefPayloadType.utf8), identifier: Data(), payload: androidAarPayloadData)
287-
288-
let ndefMessage = if itemTagType == ItemTagType.server {
289-
NFCNDEFMessage(records: [urlPayload!, androidAarPayload])
290-
} else {
291-
NFCNDEFMessage(records: [urlPayload!])
292-
}
293-
294-
return ndefMessage
295-
}
296-
297-
private func getSaveToPhotoAlbumPermissionIfNeeded(completionHandler: @escaping (Bool) -> Void) {
298-
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .authorized else {
299-
completionHandler(true)
300-
return
301-
}
302-
303-
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
304-
completionHandler(status == .authorized ? true : false)
305-
}
306-
}
307176
}

0 commit comments

Comments
 (0)