Skip to content

Commit 7561cd7

Browse files
committed
add MainViewModel
1 parent cb4fad7 commit 7561cd7

24 files changed

Lines changed: 1960 additions & 368 deletions

NativeAppTemplate.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@
130130
01A1339B2E08B0DF000AD24A /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A1339A2E08B0DF000AD24A /* MainViewModel.swift */; };
131131
01A1339D2E08B2BB000AD24A /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A1339C2E08B2BB000AD24A /* OnboardingViewModel.swift */; };
132132
01A1339F2E08B2FD000AD24A /* AcceptTermsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A1339E2E08B2FD000AD24A /* AcceptTermsViewModel.swift */; };
133+
01A133A12E08B4A5000AD24A /* ForgotPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A133A02E08B4A5000AD24A /* ForgotPasswordViewModel.swift */; };
134+
01A133A32E08B4DA000AD24A /* ResendConfirmationInstructionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A133A22E08B4DA000AD24A /* ResendConfirmationInstructionsViewModel.swift */; };
135+
01A133A52E08BB1B000AD24A /* SignInEmailAndPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A133A42E08BB1B000AD24A /* SignInEmailAndPasswordViewModel.swift */; };
136+
01A133A72E08BB66000AD24A /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A133A62E08BB66000AD24A /* SignUpViewModel.swift */; };
133137
01B37C7629B0960700BF5B2D /* ForgotPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B37C7529B0960700BF5B2D /* ForgotPasswordView.swift */; };
134138
01B526542AF4E36400655131 /* MainTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B526532AF4E36400655131 /* MainTab.swift */; };
135139
01B526562AF4E82A00655131 /* ScrollToTopID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B526552AF4E82A00655131 /* ScrollToTopID.swift */; };
@@ -302,6 +306,10 @@
302306
01A1339A2E08B0DF000AD24A /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
303307
01A1339C2E08B2BB000AD24A /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
304308
01A1339E2E08B2FD000AD24A /* AcceptTermsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptTermsViewModel.swift; sourceTree = "<group>"; };
309+
01A133A02E08B4A5000AD24A /* ForgotPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgotPasswordViewModel.swift; sourceTree = "<group>"; };
310+
01A133A22E08B4DA000AD24A /* ResendConfirmationInstructionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResendConfirmationInstructionsViewModel.swift; sourceTree = "<group>"; };
311+
01A133A42E08BB1B000AD24A /* SignInEmailAndPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInEmailAndPasswordViewModel.swift; sourceTree = "<group>"; };
312+
01A133A62E08BB66000AD24A /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = "<group>"; };
305313
01B37C7529B0960700BF5B2D /* ForgotPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgotPasswordView.swift; sourceTree = "<group>"; };
306314
01B526532AF4E36400655131 /* MainTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTab.swift; sourceTree = "<group>"; };
307315
01B526552AF4E82A00655131 /* ScrollToTopID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToTopID.swift; sourceTree = "<group>"; };
@@ -683,16 +691,20 @@
683691
012643362B3554AD00D4E9BD /* AcceptTermsView.swift */,
684692
01A1339E2E08B2FD000AD24A /* AcceptTermsViewModel.swift */,
685693
01B37C7529B0960700BF5B2D /* ForgotPasswordView.swift */,
694+
01A133A02E08B4A5000AD24A /* ForgotPasswordViewModel.swift */,
686695
0172045725AA82B4008FD63B /* MainView.swift */,
687696
01A1339A2E08B0DF000AD24A /* MainViewModel.swift */,
688697
0172045925AA82B4008FD63B /* MessageBarView.swift */,
689698
0172045825AA82B4008FD63B /* OnboardingView.swift */,
690699
01A1339C2E08B2BB000AD24A /* OnboardingViewModel.swift */,
691700
0172045D25AA82B4008FD63B /* PermissionsLoadingView.swift */,
692701
0110A1602AC81978003EDCBA /* ResendConfirmationInstructionsView.swift */,
702+
01A133A22E08B4DA000AD24A /* ResendConfirmationInstructionsViewModel.swift */,
693703
01E0A60B25BD440300298D35 /* SignInEmailAndPasswordView.swift */,
704+
01A133A42E08BB1B000AD24A /* SignInEmailAndPasswordViewModel.swift */,
694705
011586112B567363005E8E8F /* SignUpOrSignInView.swift */,
695706
01E1CECB287787A700E724FC /* SignUpView.swift */,
707+
01A133A62E08BB66000AD24A /* SignUpViewModel.swift */,
696708
0172045C25AA82B4008FD63B /* SnackbarView.swift */,
697709
01ED197A2A037B9E00CD4735 /* AppTabView.swift */,
698710
);
@@ -975,6 +987,7 @@
975987
"",
976988
"",
977989
"",
990+
"",
978991
);
979992
};
980993
/* End PBXShellScriptBuildPhase section */
@@ -1003,6 +1016,7 @@
10031016
01B526562AF4E82A00655131 /* ScrollToTopID.swift in Sources */,
10041017
0172033C25A9642E008FD63B /* NativeAppTemplateAPI.swift in Sources */,
10051018
017203B325A96FD6008FD63B /* UIApplication+DismissKeyboard.swift in Sources */,
1019+
01A133A72E08BB66000AD24A /* SignUpViewModel.swift in Sources */,
10061020
01A1339B2E08B0DF000AD24A /* MainViewModel.swift in Sources */,
10071021
0182D37825B277FA001E881D /* KeychainStore.swift in Sources */,
10081022
01FC03E22B3329B700E6CD8E /* NeedAppUpdatesView.swift in Sources */,
@@ -1021,6 +1035,7 @@
10211035
0182D38225B296B9001E881D /* ShopkeeperAdapter.swift in Sources */,
10221036
01BE4F1D29CA6F8C002008BE /* TimeZoneData.swift in Sources */,
10231037
010F86BE2622F9C900B6C62A /* ShopListView.swift in Sources */,
1038+
01A133A12E08B4A5000AD24A /* ForgotPasswordViewModel.swift in Sources */,
10241039
011586122B567363005E8E8F /* SignUpOrSignInView.swift in Sources */,
10251040
01A133992E08B052000AD24A /* AcceptPrivacyViewModel.swift in Sources */,
10261041
01E727212B020ECC004AC043 /* Bundle+Extensions.swift in Sources */,
@@ -1097,6 +1112,7 @@
10971112
01B526542AF4E36400655131 /* MainTab.swift in Sources */,
10981113
01D85B442E07ED8700A95798 /* ShopDetailViewModel.swift in Sources */,
10991114
017203CB25A97090008FD63B /* SessionController.swift in Sources */,
1115+
01A133A32E08B4DA000AD24A /* ResendConfirmationInstructionsViewModel.swift in Sources */,
11001116
0106413E29A9F1C300B46FED /* UpdatePassword.swift in Sources */,
11011117
0172787B2D7D903500CE424F /* ItemTagAdapter.swift in Sources */,
11021118
010F86AE2621A2A900B6C62A /* ShopDetailView.swift in Sources */,
@@ -1112,6 +1128,7 @@
11121128
018E21CB2B36367F00FFD1F6 /* MeRequest.swift in Sources */,
11131129
01E0A5B825BD0FCD00298D35 /* ErrorView.swift in Sources */,
11141130
0172789A2D7D99D100CE424F /* ItemTagListCardView.swift in Sources */,
1131+
01A133A52E08BB1B000AD24A /* SignInEmailAndPasswordViewModel.swift in Sources */,
11151132
0172789B2D7D99D100CE424F /* ItemTagListView.swift in Sources */,
11161133
0172789C2D7D99D100CE424F /* ItemTagDetailView.swift in Sources */,
11171134
01D85AEB2E07CF3600A95798 /* ItemTagEditViewModel.swift in Sources */,

NativeAppTemplate/Data/DataManager.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SwiftUI
1515

1616
// Repositories
1717
private(set) var onboardingRepository: OnboardingRepositoryProtocol!
18+
private(set) var signUpRepository: SignUpRepositoryProtocol!
1819
private(set) var accountPasswordRepository: AccountPasswordRepositoryProtocol!
1920
private(set) var shopRepository: ShopRepositoryProtocol!
2021
private(set) var itemTagRepository: ItemTagRepositoryProtocol!
@@ -42,6 +43,7 @@ import SwiftUI
4243
let itemTagsService = ItemTagsService(networkClient: sessionController.client)
4344

4445
onboardingRepository = OnboardingRepository()
46+
signUpRepository = SignUpRepository()
4547
accountPasswordRepository = AccountPasswordRepository(accountPasswordService: accountPasswordService)
4648
shopRepository = ShopRepository(shopsService: shopsService)
4749
itemTagRepository = ItemTagRepository(itemTagsService: itemTagsService)

NativeAppTemplate/UI/App Root/ForgotPasswordView.swift

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,92 +9,65 @@ import SwiftUI
99

1010
struct ForgotPasswordView: View {
1111
@Environment(\.dismiss) private var dismiss
12-
@Environment(MessageBus.self) private var messageBus
13-
@State var email: String = ""
14-
@State private var isSendingResetPasswordInstructions = false
15-
let signUpRepository: SignUpRepositoryProtocol
12+
@State private var viewModel: ForgotPasswordViewModel
1613

1714
init(
18-
signUpRepository: SignUpRepositoryProtocol
15+
viewModel: ForgotPasswordViewModel
1916
) {
20-
self.signUpRepository = signUpRepository
21-
}
22-
23-
private var hasInvalidData: Bool {
24-
if Utility.isBlank(email) {
25-
return true
26-
}
27-
28-
if !Utility.validateEmail(email) {
29-
return true
30-
}
31-
32-
return false
17+
self._viewModel = State(initialValue: viewModel)
3318
}
3419
}
3520

3621
extension ForgotPasswordView {
3722
var body: some View {
3823
contentView
24+
.onChange(of: viewModel.shouldDismiss) {
25+
if viewModel.shouldDismiss {
26+
dismiss()
27+
}
28+
}
3929
}
4030
}
4131

4232
// MARK: - private
4333
private extension ForgotPasswordView {
4434
var contentView: some View {
45-
35+
4636
@ViewBuilder var contentView: some View {
47-
if isSendingResetPasswordInstructions {
37+
if viewModel.isSendingResetPasswordInstructions {
4838
LoadingView()
4939
} else {
5040
forgotPasswordView
5141
}
5242
}
53-
43+
5444
return contentView
5545
}
56-
46+
5747
var forgotPasswordView: some View {
5848
Form {
5949
Section {
60-
TextField(String.placeholderEmail, text: $email)
50+
TextField(String.placeholderEmail, text: $viewModel.email)
6151
.textContentType(.emailAddress)
6252
.autocapitalization(.none)
6353
} header: {
6454
Text(String.email)
6555
} footer: {
66-
if Utility.isBlank(email) {
56+
if viewModel.isEmailBlank {
6757
Text(String.emailIsRequired)
6858
.foregroundStyle(.red)
69-
} else if !Utility.validateEmail(email) {
59+
} else if viewModel.isEmailInvalid {
7060
Text(String.emailIsInvalid)
7161
.foregroundStyle(.red)
7262
}
7363
}
74-
64+
7565
MainButtonView(title: String.buttonSendMeResetPasswordInstructions, type: .primary(withArrow: false)) {
76-
sendMeResetPasswordInstructionsTapped()
66+
viewModel.sendMeResetPasswordInstructionsTapped()
7767
}
78-
.disabled(hasInvalidData)
68+
.disabled(viewModel.hasInvalidData)
7969
.listRowBackground(Color.clear)
8070
}
8171
.navigationTitle(String.forgotYourPassword)
8272
}
83-
84-
private func sendMeResetPasswordInstructionsTapped() {
85-
let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
86-
let theEmail = email.trimmingCharacters(in: whitespacesAndNewlines)
87-
88-
Task { @MainActor in
89-
do {
90-
let sendResetPassword = SendResetPassword(email: theEmail)
91-
try await signUpRepository.sendResetPasswordInstruction(sendResetPassword: sendResetPassword)
92-
messageBus.post(message: Message(level: .success, message: .sentResetPasswordInstruction, autoDismiss: false))
93-
dismiss()
94-
} catch {
95-
UIApplication.dismissKeyboard()
96-
messageBus.post(message: Message(level: .error, message: String.sentResetPasswordInstructionError, autoDismiss: false))
97-
}
98-
}
99-
}
10073
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// ForgotPasswordViewModel.swift
3+
// NativeAppTemplate
4+
//
5+
// Created by Daisuke Adachi on 2025/06/16.
6+
//
7+
8+
import SwiftUI
9+
import Observation
10+
11+
@Observable
12+
@MainActor
13+
final class ForgotPasswordViewModel {
14+
var email = ""
15+
var shouldDismiss = false
16+
var isSendingResetPasswordInstructions = false
17+
18+
private let signUpRepository: SignUpRepositoryProtocol
19+
private let messageBus: MessageBus
20+
21+
init(
22+
signUpRepository: SignUpRepositoryProtocol,
23+
messageBus: MessageBus
24+
) {
25+
self.signUpRepository = signUpRepository
26+
self.messageBus = messageBus
27+
}
28+
29+
var hasInvalidData: Bool {
30+
if Utility.isBlank(email) {
31+
return true
32+
}
33+
34+
if !Utility.validateEmail(email) {
35+
return true
36+
}
37+
38+
return false
39+
}
40+
41+
var isEmailBlank: Bool {
42+
Utility.isBlank(email)
43+
}
44+
45+
var isEmailInvalid: Bool {
46+
!Utility.isBlank(email) && !Utility.validateEmail(email)
47+
}
48+
49+
func sendMeResetPasswordInstructionsTapped() {
50+
let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
51+
let theEmail = email.trimmingCharacters(in: whitespacesAndNewlines)
52+
53+
Task { @MainActor in
54+
isSendingResetPasswordInstructions = true
55+
56+
do {
57+
let sendResetPassword = SendResetPassword(email: theEmail)
58+
try await signUpRepository.sendResetPasswordInstruction(sendResetPassword: sendResetPassword)
59+
messageBus.post(message: Message(level: .success, message: .sentResetPasswordInstruction, autoDismiss: false))
60+
shouldDismiss = true
61+
} catch {
62+
UIApplication.dismissKeyboard()
63+
messageBus.post(message: Message(level: .error, message: String.sentResetPasswordInstructionError, autoDismiss: false))
64+
}
65+
66+
isSendingResetPasswordInstructions = false
67+
}
68+
}
69+
}

NativeAppTemplate/UI/App Root/ResendConfirmationInstructionsView.swift

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,92 +9,65 @@ import SwiftUI
99

1010
struct ResendConfirmationInstructionsView: View {
1111
@Environment(\.dismiss) private var dismiss
12-
@Environment(MessageBus.self) private var messageBus
13-
@State var email: String = ""
14-
@State private var isSendingConfirmationInstructions = false
15-
let signUpRepository: SignUpRepositoryProtocol
12+
@State private var viewModel: ResendConfirmationInstructionsViewModel
1613

1714
init(
18-
signUpRepository: SignUpRepositoryProtocol
15+
viewModel: ResendConfirmationInstructionsViewModel
1916
) {
20-
self.signUpRepository = signUpRepository
21-
}
22-
23-
private var hasInvalidData: Bool {
24-
if Utility.isBlank(email) {
25-
return true
26-
}
27-
28-
if !Utility.validateEmail(email) {
29-
return true
30-
}
31-
32-
return false
17+
self._viewModel = State(initialValue: viewModel)
3318
}
3419
}
3520

3621
extension ResendConfirmationInstructionsView {
3722
var body: some View {
3823
contentView
24+
.onChange(of: viewModel.shouldDismiss) {
25+
if viewModel.shouldDismiss {
26+
dismiss()
27+
}
28+
}
3929
}
4030
}
4131

4232
// MARK: - private
4333
private extension ResendConfirmationInstructionsView {
4434
var contentView: some View {
45-
35+
4636
@ViewBuilder var contentView: some View {
47-
if isSendingConfirmationInstructions {
37+
if viewModel.isSendingConfirmationInstructions {
4838
LoadingView()
4939
} else {
5040
resendConfirmationInstructionsView
5141
}
5242
}
53-
43+
5444
return contentView
5545
}
56-
46+
5747
var resendConfirmationInstructionsView: some View {
5848
Form {
5949
Section {
60-
TextField(String.placeholderEmail, text: $email)
50+
TextField(String.placeholderEmail, text: $viewModel.email)
6151
.textContentType(.emailAddress)
6252
.autocapitalization(.none)
6353
} header: {
6454
Text(String.email)
6555
} footer: {
66-
if Utility.isBlank(email) {
56+
if viewModel.isEmailBlank {
6757
Text(String.emailIsRequired)
6858
.foregroundStyle(.red)
69-
} else if !Utility.validateEmail(email) {
59+
} else if viewModel.isEmailInvalid {
7060
Text(String.emailIsInvalid)
7161
.foregroundStyle(.red)
7262
}
7363
}
7464

7565
MainButtonView(title: String.buttonSendMeConfirmationInstructions, type: .primary(withArrow: false)) {
76-
sendMeConfirmationInstructionsTapped()
66+
viewModel.sendMeConfirmationInstructionsTapped()
7767
}
78-
.disabled(hasInvalidData)
68+
.disabled(viewModel.hasInvalidData)
7969
.listRowBackground(Color.clear)
8070
}
8171
.navigationTitle(String.didntReceiveConfirmationInstructions)
8272
}
83-
84-
private func sendMeConfirmationInstructionsTapped() {
85-
let whitespacesAndNewlines = CharacterSet.whitespacesAndNewlines
86-
let theEmail = email.trimmingCharacters(in: whitespacesAndNewlines)
87-
88-
Task { @MainActor in
89-
do {
90-
let sendConfirmation = SendConfirmation(email: theEmail)
91-
try await signUpRepository.sendConfirmationInstruction(sendConfirmation: sendConfirmation)
92-
messageBus.post(message: Message(level: .success, message: .sentConfirmationInstruction, autoDismiss: false))
93-
dismiss()
94-
} catch {
95-
UIApplication.dismissKeyboard()
96-
messageBus.post(message: Message(level: .error, message: String.sentConfirmationInstructionError, autoDismiss: false))
97-
}
98-
}
99-
}
10073
}

0 commit comments

Comments
 (0)