@@ -11,44 +11,21 @@ import CoreNFC
1111
1212struct 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