Skip to content

Commit 77cb706

Browse files
authored
Merge pull request #13 from nativeapptemplate/add_other_viewModels3
add ShopDetailViewModel
2 parents 8ad2e54 + 5541166 commit 77cb706

File tree

15 files changed

+1796
-352
lines changed

15 files changed

+1796
-352
lines changed

NativeAppTemplate.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@
140140
01D85AEB2E07CF3600A95798 /* ItemTagEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AEA2E07CF3600A95798 /* ItemTagEditViewModel.swift */; };
141141
01D85AEF2E07D20500A95798 /* ItemTagCreateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AEE2E07D20500A95798 /* ItemTagCreateViewModel.swift */; };
142142
01D85AF32E07D37E00A95798 /* ItemTagListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85AF22E07D37E00A95798 /* ItemTagListViewModel.swift */; };
143+
01D85B442E07ED8700A95798 /* ShopDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85B432E07ED8700A95798 /* ShopDetailViewModel.swift */; };
144+
01D85B462E07F15400A95798 /* PasswordEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85B452E07F15400A95798 /* PasswordEditViewModel.swift */; };
145+
01D85B482E07F16100A95798 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85B472E07F16100A95798 /* SettingsViewModel.swift */; };
146+
01D85B4A2E07F16900A95798 /* ShopkeeperEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D85B492E07F16900A95798 /* ShopkeeperEditViewModel.swift */; };
143147
01D8AE8B2AB453C1009AFFBA /* ShopBasicSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */; };
144148
01DCE23F298FA3B300BA311D /* ShopListCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DCE23E298FA3B300BA311D /* ShopListCardView.swift */; };
145149
01E0A59C25BD088600298D35 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E0A59125BD087E00298D35 /* SettingsView.swift */; };
@@ -304,6 +308,10 @@
304308
01D85AEA2E07CF3600A95798 /* ItemTagEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagEditViewModel.swift; sourceTree = "<group>"; };
305309
01D85AEE2E07D20500A95798 /* ItemTagCreateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagCreateViewModel.swift; sourceTree = "<group>"; };
306310
01D85AF22E07D37E00A95798 /* ItemTagListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTagListViewModel.swift; sourceTree = "<group>"; };
311+
01D85B432E07ED8700A95798 /* ShopDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopDetailViewModel.swift; sourceTree = "<group>"; };
312+
01D85B452E07F15400A95798 /* PasswordEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordEditViewModel.swift; sourceTree = "<group>"; };
313+
01D85B472E07F16100A95798 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
314+
01D85B492E07F16900A95798 /* ShopkeeperEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopkeeperEditViewModel.swift; sourceTree = "<group>"; };
307315
01D8AE8A2AB453C1009AFFBA /* ShopBasicSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopBasicSettingsView.swift; sourceTree = "<group>"; };
308316
01DCE23E298FA3B300BA311D /* ShopListCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopListCardView.swift; sourceTree = "<group>"; };
309317
01E0A59125BD087E00298D35 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
@@ -356,6 +364,7 @@
356364
children = (
357365
0172787E2D7D933000CE424F /* ShopDetailCardView.swift */,
358366
010F86AD2621A2A900B6C62A /* ShopDetailView.swift */,
367+
01D85B432E07ED8700A95798 /* ShopDetailViewModel.swift */,
359368
);
360369
path = "Shop Detail";
361370
sourceTree = "<group>";
@@ -787,8 +796,11 @@
787796
isa = PBXGroup;
788797
children = (
789798
0106414329AA061100B46FED /* PasswordEditView.swift */,
799+
01D85B452E07F15400A95798 /* PasswordEditViewModel.swift */,
790800
01E0A59125BD087E00298D35 /* SettingsView.swift */,
801+
01D85B472E07F16100A95798 /* SettingsViewModel.swift */,
791802
01EE363D29A6DCEB009BCD9D /* ShopkeeperEditView.swift */,
803+
01D85B492E07F16900A95798 /* ShopkeeperEditViewModel.swift */,
792804
);
793805
path = Settings;
794806
sourceTree = "<group>";
@@ -945,6 +957,7 @@
945957
"",
946958
"",
947959
"",
960+
"",
948961
);
949962
};
950963
/* End PBXShellScriptBuildPhase section */
@@ -955,6 +968,7 @@
955968
files = (
956969
0172047925AA8335008FD63B /* UIFont+Extensions.swift in Sources */,
957970
01E2477029A570D300D4B00D /* SignUp.swift in Sources */,
971+
01D85B4A2E07F16900A95798 /* ShopkeeperEditViewModel.swift in Sources */,
958972
0172785A2D7D83B600CE424F /* NFCManager.swift in Sources */,
959973
0172785B2D7D83B600CE424F /* AppSingletons.swift in Sources */,
960974
017278732D7D87EB00CE424F /* UIImage+Extentions.swift in Sources */,
@@ -1051,13 +1065,15 @@
10511065
018E21CD2B36377800FFD1F6 /* MeService.swift in Sources */,
10521066
0106414029A9F2EC00B46FED /* AccountPasswordService.swift in Sources */,
10531067
01482FA42B351E4100A56D43 /* AcceptPrivacyView.swift in Sources */,
1068+
01D85B462E07F15400A95798 /* PasswordEditViewModel.swift in Sources */,
10541069
0172046325AA82BF008FD63B /* OnboardingView.swift in Sources */,
10551070
013DE735284E99DF00528CC5 /* ShopCreateView.swift in Sources */,
10561071
01D85AE72E07CD4400A95798 /* ItemTagDetailViewModel.swift in Sources */,
10571072
017203B625A96FD6008FD63B /* View+Extensions.swift in Sources */,
10581073
0106414229A9F51700B46FED /* AccountPasswordRepository.swift in Sources */,
10591074
0172033A25A9642E008FD63B /* JSONAPIDocument.swift in Sources */,
10601075
01B526542AF4E36400655131 /* MainTab.swift in Sources */,
1076+
01D85B442E07ED8700A95798 /* ShopDetailViewModel.swift in Sources */,
10611077
017203CB25A97090008FD63B /* SessionController.swift in Sources */,
10621078
0106413E29A9F1C300B46FED /* UpdatePassword.swift in Sources */,
10631079
0172787B2D7D903500CE424F /* ItemTagAdapter.swift in Sources */,
@@ -1094,6 +1110,7 @@
10941110
01E2477229A5E30400D4B00D /* Utility.swift in Sources */,
10951111
0172046725AA82BF008FD63B /* MainView.swift in Sources */,
10961112
0172033825A9642E008FD63B /* JSONAPIError.swift in Sources */,
1113+
01D85B482E07F16100A95798 /* SettingsViewModel.swift in Sources */,
10971114
);
10981115
};
10991116
01D19B3F2D4DE33500BDEAB7 /* Sources */ = {

NativeAppTemplate/UI/App Root/MainView.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,14 @@ private extension MainView {
166166
}
167167

168168
func settingsView() -> SettingsView {
169-
.init(accountPasswordRepository: dataManager.accountPasswordRepository)
169+
.init(
170+
viewModel: SettingsViewModel(
171+
sessionController: sessionController,
172+
tabViewModel: tabViewModel,
173+
messageBus: messageBus
174+
),
175+
accountPasswordRepository: dataManager.accountPasswordRepository
176+
)
170177
}
171178

172179
func handleBackgroundTagReading(_ userActivity: NSUserActivity) {

NativeAppTemplate/UI/Settings/PasswordEditView.swift

Lines changed: 19 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,20 @@
88
import SwiftUI
99

1010
struct PasswordEditView: View {
11-
@Environment(MessageBus.self) private var messageBus
1211
@Environment(\.dismiss) private var dismiss
13-
@State private var isUpdating = false
14-
@State private var currentPassword: String = ""
15-
@State private var password: String = ""
16-
@State private var passwordConfirmation: String = ""
17-
private var accountPasswordRepository: AccountPasswordRepositoryProtocol
12+
@State private var viewModel: PasswordEditViewModel
1813

19-
init(
20-
accountPasswordRepository: AccountPasswordRepositoryProtocol
21-
) {
22-
self.accountPasswordRepository = accountPasswordRepository
23-
}
24-
25-
private var hasInvalidData: Bool {
26-
if Utility.isBlank(currentPassword) ||
27-
Utility.isBlank(password) ||
28-
Utility.isBlank(passwordConfirmation) {
29-
return true
30-
}
31-
32-
if hasInvalidDataPassword {
33-
return true
34-
}
35-
36-
return false
37-
}
38-
39-
private var hasInvalidDataPassword: Bool {
40-
if Utility.isBlank(password) {
41-
return true
42-
}
43-
44-
if password.count < .minimumPasswordLength {
45-
return true
46-
}
47-
48-
return false
14+
init(viewModel: PasswordEditViewModel) {
15+
self._viewModel = State(wrappedValue: viewModel)
4916
}
5017

5118
var body: some View {
5219
contentView
20+
.onChange(of: viewModel.shouldDismiss) { _, shouldDismiss in
21+
if shouldDismiss {
22+
dismiss()
23+
}
24+
}
5325
}
5426
}
5527

@@ -58,7 +30,7 @@ private extension PasswordEditView {
5830
var contentView: some View {
5931

6032
@ViewBuilder var contentView: some View {
61-
if isUpdating {
33+
if viewModel.isBusy {
6234
LoadingView()
6335
} else {
6436
passwordEditView
@@ -71,7 +43,7 @@ private extension PasswordEditView {
7143
var passwordEditView: some View {
7244
Form {
7345
Section {
74-
SecureField(String.currentPassword, text: $currentPassword)
46+
SecureField(String.currentPassword, text: $viewModel.currentPassword)
7547
.textContentType(.password)
7648
.autocapitalization(.none)
7749
.autocorrectionDisabled(true)
@@ -82,35 +54,35 @@ private extension PasswordEditView {
8254
Text(String.weNeedYourCurrentPassword)
8355
.font(.uiFootnote)
8456
Text(String.currentPasswordIsRequired)
85-
.foregroundStyle(Utility.isBlank(currentPassword) ? .red : .clear)
57+
.foregroundStyle(Utility.isBlank(viewModel.currentPassword) ? .red : .clear)
8658
.font(.uiFootnote)
8759
}
8860
}
8961
Section {
90-
SecureField(String.newPassword, text: $password)
62+
SecureField(String.newPassword, text: $viewModel.password)
9163
.textContentType(.password)
9264
.autocapitalization(.none)
9365
.autocorrectionDisabled(true)
9466
} header: {
9567
Text(String.newPassword)
9668
} footer: {
9769
VStack(alignment: .leading) {
98-
Text("\(Int.minimumPasswordLength) characters minimum.")
70+
Text("\(viewModel.minimumPasswordLength) characters minimum.")
9971
.font(.uiFootnote)
10072

101-
if Utility.isBlank(password) {
73+
if Utility.isBlank(viewModel.password) {
10274
Text(String.newPasswordIsRequired)
10375
.foregroundStyle(.red)
10476
.font(.uiFootnote)
105-
} else if hasInvalidDataPassword {
77+
} else if viewModel.hasInvalidDataPassword {
10678
Text(String.passwordIsInvalid)
10779
.foregroundStyle(.red)
10880
.font(.uiFootnote)
10981
}
11082
}
11183
}
11284
Section {
113-
SecureField(String.confirmNewPassword, text: $passwordConfirmation)
85+
SecureField(String.confirmNewPassword, text: $viewModel.passwordConfirmation)
11486
.textContentType(.password)
11587
.autocapitalization(.none)
11688
.autocorrectionDisabled(true)
@@ -119,46 +91,19 @@ private extension PasswordEditView {
11991
} footer: {
12092
Text(String.confirmNewPasswordIsRequired)
12193
.font(.uiFootnote)
122-
.foregroundStyle(Utility.isBlank(passwordConfirmation) ? .red : .clear)
94+
.foregroundStyle(Utility.isBlank(viewModel.passwordConfirmation) ? .red : .clear)
12395
}
12496
}
12597
.navigationTitle(String.updatePassword)
12698
.toolbar {
12799
ToolbarItem(placement: .navigationBarTrailing) {
128100
Button {
129-
updatePassword()
101+
viewModel.updatePassword()
130102
} label: {
131103
Text(String.save)
132104
}
133-
.disabled(hasInvalidData)
134-
}
135-
}
136-
}
137-
138-
func updatePassword() {
139-
let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
140-
let theCurrentPassword = currentPassword.trimmingCharacters(in: whitespacesAndNewlines)
141-
let thePassword = password.trimmingCharacters(in: whitespacesAndNewlines)
142-
let thePasswordConfirmation = passwordConfirmation.trimmingCharacters(in: whitespacesAndNewlines)
143-
144-
Task { @MainActor in
145-
isUpdating = true
146-
147-
do {
148-
let updatePassword = UpdatePassword(
149-
currentPassword: theCurrentPassword,
150-
password: thePassword,
151-
passwordConfirmation: thePasswordConfirmation
152-
)
153-
154-
try await accountPasswordRepository.update(updatePassword: updatePassword)
155-
messageBus.post(message: Message(level: .success, message: .passwordUpdated))
156-
dismiss()
157-
} catch {
158-
messageBus.post(message: Message(level: .error, message: error.localizedDescription, autoDismiss: false))
105+
.disabled(viewModel.hasInvalidData)
159106
}
160-
161-
isUpdating = false
162107
}
163108
}
164109
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// PasswordEditViewModel.swift
3+
// NativeAppTemplate
4+
//
5+
// Created by Daisuke Adachi on 2025/06/15.
6+
//
7+
8+
import SwiftUI
9+
import Observation
10+
11+
@Observable
12+
@MainActor
13+
final class PasswordEditViewModel {
14+
var currentPassword = ""
15+
var password = ""
16+
var passwordConfirmation = ""
17+
var isUpdating = false
18+
var shouldDismiss = false
19+
20+
private let accountPasswordRepository: AccountPasswordRepositoryProtocol
21+
private let messageBus: MessageBus
22+
23+
init(
24+
accountPasswordRepository: AccountPasswordRepositoryProtocol,
25+
messageBus: MessageBus
26+
) {
27+
self.accountPasswordRepository = accountPasswordRepository
28+
self.messageBus = messageBus
29+
}
30+
31+
var isBusy: Bool {
32+
isUpdating
33+
}
34+
35+
var hasInvalidData: Bool {
36+
if Utility.isBlank(currentPassword) ||
37+
Utility.isBlank(password) ||
38+
Utility.isBlank(passwordConfirmation) {
39+
return true
40+
}
41+
42+
if hasInvalidDataPassword {
43+
return true
44+
}
45+
46+
return false
47+
}
48+
49+
var hasInvalidDataPassword: Bool {
50+
if Utility.isBlank(password) {
51+
return true
52+
}
53+
54+
if password.count < .minimumPasswordLength {
55+
return true
56+
}
57+
58+
return false
59+
}
60+
61+
var minimumPasswordLength: Int {
62+
.minimumPasswordLength
63+
}
64+
65+
func updatePassword() {
66+
let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
67+
let theCurrentPassword = currentPassword.trimmingCharacters(in: whitespacesAndNewlines)
68+
let thePassword = password.trimmingCharacters(in: whitespacesAndNewlines)
69+
let thePasswordConfirmation = passwordConfirmation.trimmingCharacters(in: whitespacesAndNewlines)
70+
71+
Task {
72+
isUpdating = true
73+
74+
do {
75+
let updatePassword = UpdatePassword(
76+
currentPassword: theCurrentPassword,
77+
password: thePassword,
78+
passwordConfirmation: thePasswordConfirmation
79+
)
80+
81+
try await accountPasswordRepository.update(updatePassword: updatePassword)
82+
messageBus.post(message: Message(level: .success, message: .passwordUpdated))
83+
shouldDismiss = true
84+
} catch {
85+
messageBus.post(message: Message(level: .error, message: error.localizedDescription, autoDismiss: false))
86+
}
87+
88+
isUpdating = false
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)