Skip to content

Commit a5f7de7

Browse files
authored
Merge pull request #11 from nativeapptemplate/add_other_viewModels
add ShopSettingsViewModel
2 parents 9657d5c + b60ec70 commit a5f7de7

23 files changed

+1259
-300
lines changed

NativeAppTemplate.xcodeproj/project.pbxproj

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
0110A15F2AC816F5003EDCBA /* SendConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0110A15E2AC816F5003EDCBA /* SendConfirmation.swift */; };
1919
0110A1612AC81978003EDCBA /* ResendConfirmationInstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0110A1602AC81978003EDCBA /* ResendConfirmationInstructionsView.swift */; };
2020
0114F3AC2E079BD100F4A1DD /* ShopListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0114F3AB2E079BD100F4A1DD /* ShopListViewModel.swift */; };
21+
0114F4032E07A88000F4A1DD /* ShopCreateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0114F4022E07A88000F4A1DD /* ShopCreateViewModel.swift */; };
2122
011586122B567363005E8E8F /* SignUpOrSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011586112B567363005E8E8F /* SignUpOrSignInView.swift */; };
2223
011DDC21287669EA00C6C21F /* SignUpRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011DDC20287669EA00C6C21F /* SignUpRepository.swift */; };
2324
011DDC2328766C5E00C6C21F /* SignUpService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011DDC2228766C5D00C6C21F /* SignUpService.swift */; };
@@ -132,6 +133,9 @@
132133
01B6F5AB2601F84700397E66 /* PermissionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B6F5AA2601F84700397E66 /* PermissionsRequest.swift */; };
133134
01B9E45228A5070D00CAC681 /* ShopkeeperSignInAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B9E45128A5070D00CAC681 /* ShopkeeperSignInAdapter.swift */; };
134135
01BE4F1D29CA6F8C002008BE /* TimeZoneData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BE4F1C29CA6F8C002008BE /* TimeZoneData.swift */; };
136+
01D85A962E07C78400A95798 /* NumberTagsWebpageListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A952E07C78400A95798 /* NumberTagsWebpageListViewModel.swift */; };
137+
01D85A9A2E07C85900A95798 /* ShopBasicSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A992E07C85900A95798 /* ShopBasicSettingsViewModel.swift */; };
138+
01D85A9E2E07C9BD00A95798 /* ShopSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85A9D2E07C9BD00A95798 /* ShopSettingsViewModel.swift */; };
135139
01D8AE8B2AB453C1009AFFBA /* ShopBasicSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */; };
136140
01DCE23F298FA3B300BA311D /* ShopListCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DCE23E298FA3B300BA311D /* ShopListCardView.swift */; };
137141
01E0A59C25BD088600298D35 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E0A59125BD087E00298D35 /* SettingsView.swift */; };
@@ -174,6 +178,7 @@
174178
0110A15E2AC816F5003EDCBA /* SendConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendConfirmation.swift; sourceTree = "<group>"; };
175179
0110A1602AC81978003EDCBA /* ResendConfirmationInstructionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResendConfirmationInstructionsView.swift; sourceTree = "<group>"; };
176180
0114F3AB2E079BD100F4A1DD /* ShopListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopListViewModel.swift; sourceTree = "<group>"; };
181+
0114F4022E07A88000F4A1DD /* ShopCreateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopCreateViewModel.swift; sourceTree = "<group>"; };
177182
011586112B567363005E8E8F /* SignUpOrSignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpOrSignInView.swift; sourceTree = "<group>"; };
178183
011DDC20287669EA00C6C21F /* SignUpRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpRepository.swift; sourceTree = "<group>"; };
179184
011DDC2228766C5D00C6C21F /* SignUpService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpService.swift; sourceTree = "<group>"; };
@@ -288,6 +293,9 @@
288293
01B9E45128A5070D00CAC681 /* ShopkeeperSignInAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopkeeperSignInAdapter.swift; sourceTree = "<group>"; };
289294
01BE4F1C29CA6F8C002008BE /* TimeZoneData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneData.swift; sourceTree = "<group>"; };
290295
01D19B432D4DE33500BDEAB7 /* NativeAppTemplateTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NativeAppTemplateTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
296+
01D85A952E07C78400A95798 /* NumberTagsWebpageListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberTagsWebpageListViewModel.swift; sourceTree = "<group>"; };
297+
01D85A992E07C85900A95798 /* ShopBasicSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopBasicSettingsViewModel.swift; sourceTree = "<group>"; };
298+
01D85A9D2E07C9BD00A95798 /* ShopSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopSettingsViewModel.swift; sourceTree = "<group>"; };
291299
01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopBasicSettingsView.swift; sourceTree = "<group>"; };
292300
01DCE23E298FA3B300BA311D /* ShopListCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopListCardView.swift; sourceTree = "<group>"; };
293301
01E0A59125BD087E00298D35 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
@@ -428,9 +436,14 @@
428436
01467355299901E50005423D /* Shop Settings */ = {
429437
isa = PBXGroup;
430438
children = (
439+
017278952D7D99D100CE424F /* ItemTag Detail */,
440+
017278992D7D99D100CE424F /* ItemTag List */,
431441
017278912D7D99B900CE424F /* NumberTagsWebpageListView.swift */,
442+
01D85A952E07C78400A95798 /* NumberTagsWebpageListViewModel.swift */,
432443
01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */,
444+
01D85A992E07C85900A95798 /* ShopBasicSettingsViewModel.swift */,
433445
01467356299902230005423D /* ShopSettingsView.swift */,
446+
01D85A9D2E07C9BD00A95798 /* ShopSettingsViewModel.swift */,
434447
);
435448
path = "Shop Settings";
436449
sourceTree = "<group>";
@@ -439,11 +452,10 @@
439452
isa = PBXGroup;
440453
children = (
441454
013DE734284E99DF00528CC5 /* ShopCreateView.swift */,
455+
0114F4022E07A88000F4A1DD /* ShopCreateViewModel.swift */,
442456
01DCE23E298FA3B300BA311D /* ShopListCardView.swift */,
443457
010F86BD2622F9C900B6C62A /* ShopListView.swift */,
444458
0114F3AB2E079BD100F4A1DD /* ShopListViewModel.swift */,
445-
017278952D7D99D100CE424F /* ItemTag Detail */,
446-
017278992D7D99D100CE424F /* ItemTag List */,
447459
);
448460
path = "Shop List";
449461
sourceTree = "<group>";
@@ -918,6 +930,8 @@
918930
"",
919931
"",
920932
"",
933+
"",
934+
"",
921935
);
922936
};
923937
/* End PBXShellScriptBuildPhase section */
@@ -975,6 +989,7 @@
975989
01E0A5B625BD0FCD00298D35 /* LoadingView.swift in Sources */,
976990
0114F3AC2E079BD100F4A1DD /* ShopListViewModel.swift in Sources */,
977991
0172051A25AAF6C0008FD63B /* SessionsService.swift in Sources */,
992+
01D85A962E07C78400A95798 /* NumberTagsWebpageListViewModel.swift in Sources */,
978993
017204D125AA8479008FD63B /* DataState.swift in Sources */,
979994
012643372B3554AD00D4E9BD /* AcceptTermsView.swift in Sources */,
980995
0172033E25A9642E008FD63B /* Parameters.swift in Sources */,
@@ -998,6 +1013,7 @@
9981013
0172788D2D7D936E00CE424F /* CustomerScannedTag.swift in Sources */,
9991014
017278902D7D936E00CE424F /* TagView.swift in Sources */,
10001015
0106414429AA061100B46FED /* PasswordEditView.swift in Sources */,
1016+
0114F4032E07A88000F4A1DD /* ShopCreateViewModel.swift in Sources */,
10011017
0172786B2D7D840A00CE424F /* ShowTagInfoScanResult.swift in Sources */,
10021018
017204D925AA847E008FD63B /* ShopRepository.swift in Sources */,
10031019
017278612D7D83E700CE424F /* ItemTagData.swift in Sources */,
@@ -1012,9 +1028,11 @@
10121028
01E0A63025BD53FD00298D35 /* Shop.swift in Sources */,
10131029
017278072D7D4F5800CE424F /* OnboardingRepository.swift in Sources */,
10141030
0135E7192D7E33F9004AD8FA /* CompleteScanResultView.swift in Sources */,
1031+
01D85A9A2E07C85900A95798 /* ShopBasicSettingsViewModel.swift in Sources */,
10151032
0135E71A2D7E33F9004AD8FA /* ShowTagInfoScanResultView.swift in Sources */,
10161033
0135E71B2D7E33F9004AD8FA /* ScanView.swift in Sources */,
10171034
013292BE262C3EA400690B75 /* LoggedInShopkeeper.swift in Sources */,
1035+
01D85A9E2E07C9BD00A95798 /* ShopSettingsViewModel.swift in Sources */,
10181036
0172035825A9642E008FD63B /* ShopsService.swift in Sources */,
10191037
018E21CD2B36377800FFD1F6 /* MeService.swift in Sources */,
10201038
0106414029A9F2EC00B46FED /* AccountPasswordService.swift in Sources */,

NativeAppTemplate/UI/App Root/MainView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ private extension MainView {
153153
shopRepository: dataManager.shopRepository,
154154
itemTagRepository: dataManager.itemTagRepository,
155155
tabViewModel: tabViewModel,
156-
mainTab: .shops
156+
mainTab: .shops,
157+
messageBus: messageBus
157158
)
158159
return ShopListView(viewModel: viewModel)
159160
}

NativeAppTemplate/UI/Shop Detail/ShopDetailView.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,13 @@ private extension ShopDetailView {
169169
ToolbarItem(placement: .navigationBarTrailing) {
170170
NavigationLink(
171171
destination: ShopSettingsView(
172-
shopRepository: shopRepository,
173-
itemTagRepository: itemTagRepository,
174-
shopId: shop.wrappedValue.id
172+
viewModel: ShopSettingsViewModel(
173+
sessionController: sessionController,
174+
shopRepository: shopRepository,
175+
itemTagRepository: itemTagRepository,
176+
messageBus: messageBus,
177+
shopId: shop.wrappedValue.id
178+
)
175179
)
176180
) {
177181
Image(systemName: "gearshape.fill")

NativeAppTemplate/UI/Shop List/ShopCreateView.swift

Lines changed: 25 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,47 @@ import SwiftUI
99

1010
struct ShopCreateView: View {
1111
@Environment(\.dismiss) private var dismiss
12-
@Environment(\.sessionController) private var sessionController
13-
@Environment(MessageBus.self) private var messageBus
14-
private var shopRepository: ShopRepositoryProtocol
15-
@State private var name = ""
16-
@State private var description = ""
17-
@State private var selectedTimeZone: String
18-
@State private var isCreating = false
12+
@State private var viewModel: ShopCreateViewModel
1913

20-
init(
21-
shopRepository: ShopRepositoryProtocol
22-
) {
23-
self.shopRepository = shopRepository
24-
_selectedTimeZone = State(initialValue: Utility.currentTimeZone())
14+
init(viewModel: ShopCreateViewModel) {
15+
self._viewModel = State(wrappedValue: viewModel)
2516
}
2617

27-
private var hasInvalidData: Bool { Utility.isBlank(name) }
28-
2918
var body: some View {
3019
contentView
20+
.onChange(of: viewModel.shouldDismiss) {
21+
if viewModel.shouldDismiss {
22+
dismiss()
23+
}
24+
}
3125
}
32-
}
33-
34-
// MARK: - private
35-
private extension ShopCreateView {
36-
var contentView: some View {
3726

38-
@ViewBuilder var contentView: some View {
39-
if isCreating {
40-
LoadingView()
41-
} else {
42-
shopCreateView
43-
}
27+
@ViewBuilder
28+
private var contentView: some View {
29+
if viewModel.isCreating {
30+
LoadingView()
31+
} else {
32+
shopCreateForm
4433
}
45-
46-
return contentView
4734
}
4835

49-
private var shopCreateView: some View {
36+
private var shopCreateForm: some View {
5037
NavigationStack {
5138
Form {
5239
Section {
53-
TextField(String.name, text: $name)
40+
TextField(String.name, text: $viewModel.name)
5441
} footer: {
5542
Text(String.shopNameIsRequired)
56-
.foregroundStyle(Utility.isBlank(name) ? .red : .clear)
43+
.foregroundStyle(viewModel.hasInvalidData ? .red : .clear)
5744
}
5845

5946
Section {
60-
TextField(String.descriptionString, text: $description, axis: .vertical)
47+
TextField(String.descriptionString, text: $viewModel.description, axis: .vertical)
6148
.lineLimit(10, reservesSpace: true)
6249
}
63-
50+
6451
Section {
65-
Picker(String.timeZone, selection: $selectedTimeZone) {
52+
Picker(String.timeZone, selection: $viewModel.selectedTimeZone) {
6653
ForEach(timeZones.keys, id: \.self) { key in
6754
Text(timeZones[key]!).tag(key)
6855
}
@@ -72,54 +59,18 @@ private extension ShopCreateView {
7259
.navigationTitle(String.addShop)
7360
.toolbar {
7461
ToolbarItem(placement: .navigationBarTrailing) {
75-
Button {
76-
createShop()
77-
} label: {
78-
Text(String.save)
62+
Button(String.save) {
63+
viewModel.createShop()
7964
}
80-
.disabled(hasInvalidData)
65+
.disabled(viewModel.hasInvalidData)
8166
}
67+
8268
ToolbarItem(placement: .navigationBarLeading) {
83-
Button {
69+
Button(String.cancel) {
8470
dismiss()
85-
} label: {
86-
Text(String.cancel)
8771
}
8872
}
8973
}
9074
}
9175
}
92-
93-
func createShop() {
94-
Task { @MainActor in
95-
isCreating = true
96-
97-
do {
98-
let shop = Shop(
99-
id: "",
100-
name: name,
101-
description: description,
102-
timeZone: selectedTimeZone
103-
)
104-
_ = try await shopRepository.create(shop: shop)
105-
messageBus.post(message: Message(level: .success, message: .shopCreated))
106-
} catch {
107-
messageBus.post(
108-
message: Message(
109-
level: .error,
110-
message: error.localizedDescription,
111-
autoDismiss: false
112-
)
113-
)
114-
115-
// e.g. Limit shopps count error
116-
guard case NativeAppTemplateAPIError.requestFailed(_, 422, _) = error else {
117-
try await sessionController.logout()
118-
return
119-
}
120-
}
121-
122-
dismiss()
123-
}
124-
}
12576
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// ShopCreateViewModel.swift
3+
// NativeAppTemplate
4+
//
5+
// Created by Claude on 2025/06/22.
6+
//
7+
8+
import Foundation
9+
import Observation
10+
import SwiftUI
11+
12+
@Observable
13+
@MainActor
14+
final class ShopCreateViewModel {
15+
var name: String = ""
16+
var description: String = ""
17+
var selectedTimeZone: String = Utility.currentTimeZone()
18+
var isCreating = false
19+
20+
private let sessionController: SessionControllerProtocol
21+
private let shopRepository: ShopRepositoryProtocol
22+
private(set) var messageBus: MessageBus
23+
var shouldDismiss: Bool = false
24+
25+
init(
26+
sessionController: SessionControllerProtocol,
27+
shopRepository: ShopRepositoryProtocol,
28+
messageBus: MessageBus
29+
) {
30+
self.sessionController = sessionController
31+
self.shopRepository = shopRepository
32+
self.messageBus = messageBus
33+
}
34+
35+
var hasInvalidData: Bool {
36+
Utility.isBlank(name)
37+
}
38+
39+
func createShop() {
40+
Task {
41+
isCreating = true
42+
43+
do {
44+
let shop = Shop(
45+
id: "",
46+
name: name,
47+
description: description,
48+
timeZone: selectedTimeZone
49+
)
50+
_ = try await shopRepository.create(shop: shop)
51+
messageBus.post(message: Message(level: .success, message: .shopCreated))
52+
shouldDismiss = true
53+
} catch {
54+
messageBus.post(message: Message(level: .error, message: error.localizedDescription, autoDismiss: false))
55+
56+
// e.g. Limit shops count error
57+
guard case NativeAppTemplateAPIError.requestFailed(_, 422, _) = error else {
58+
try await sessionController.logout()
59+
return
60+
}
61+
62+
shouldDismiss = true
63+
}
64+
}
65+
}
66+
}

NativeAppTemplate/UI/Shop List/ShopListView.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,11 @@ struct TapShopBelowTip: Tip {
4444
}
4545

4646
struct ShopListView: View {
47+
@Environment(DataManager.self) private var dataManager
48+
@Environment(MessageBus.self) private var messageBus
49+
4750
@State private var viewModel: ShopListViewModel
48-
51+
4952
init(viewModel: ShopListViewModel) {
5053
self._viewModel = State(wrappedValue: viewModel)
5154
}
@@ -157,7 +160,13 @@ private extension ShopListView {
157160
onDismiss: {
158161
viewModel.reload()
159162
}, content: {
160-
ShopCreateView(shopRepository: viewModel.shopRepository)
163+
ShopCreateView(
164+
viewModel: ShopCreateViewModel(
165+
sessionController: dataManager.sessionController,
166+
shopRepository: dataManager.shopRepository,
167+
messageBus: messageBus
168+
)
169+
)
161170
}
162171
)
163172
}

NativeAppTemplate/UI/Shop List/ShopListViewModel.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,22 @@ final class ShopListViewModel {
2626
private let sessionController: SessionControllerProtocol
2727
private let tabViewModel: TabViewModel
2828
private let mainTab: MainTab
29+
private let messageBus: MessageBus
2930

3031
init(
3132
sessionController: SessionControllerProtocol,
3233
shopRepository: ShopRepositoryProtocol,
3334
itemTagRepository: ItemTagRepositoryProtocol,
3435
tabViewModel: TabViewModel,
35-
mainTab: MainTab
36+
mainTab: MainTab,
37+
messageBus: MessageBus
3638
) {
3739
self.sessionController = sessionController
3840
self.shopRepository = shopRepository
3941
self.itemTagRepository = itemTagRepository
4042
self.tabViewModel = tabViewModel
4143
self.mainTab = mainTab
44+
self.messageBus = messageBus
4245
}
4346

4447
func reload() {

NativeAppTemplate/UI/Shop List/ItemTag Detail/ItemTagDetailView.swift renamed to NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagDetailView.swift

File renamed without changes.

NativeAppTemplate/UI/Shop List/ItemTag Detail/ItemTagEditView.swift renamed to NativeAppTemplate/UI/Shop Settings/ItemTag Detail/ItemTagEditView.swift

File renamed without changes.

NativeAppTemplate/UI/Shop List/ItemTag List/ItemTagCreateView.swift renamed to NativeAppTemplate/UI/Shop Settings/ItemTag List/ItemTagCreateView.swift

File renamed without changes.

0 commit comments

Comments
 (0)