Skip to content

Commit 5b5f467

Browse files
committed
✨ Sign in with email / password
Signed-off-by: Peter Friese <[email protected]>
1 parent a2b9592 commit 5b5f467

File tree

11 files changed

+163
-75
lines changed

11 files changed

+163
-75
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ scripts
108108
xcodebuild.log
109109

110110
Package.resolved
111+
GoogleService-Info.plist

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix.xcodeproj/project.pbxproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
objectVersion = 77;
77
objects = {
88

9+
/* Begin PBXBuildFile section */
10+
8854EC512CC6AC2700A09F27 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8854EC502CC6AC2700A09F27 /* FirebaseAuth */; };
11+
/* End PBXBuildFile section */
12+
913
/* Begin PBXFileReference section */
1014
88A9E6342CA834C600B3C4EF /* FriendlyFlix.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FriendlyFlix.app; sourceTree = BUILT_PRODUCTS_DIR; };
1115
/* End PBXFileReference section */
@@ -23,6 +27,7 @@
2327
isa = PBXFrameworksBuildPhase;
2428
buildActionMask = 2147483647;
2529
files = (
30+
8854EC512CC6AC2700A09F27 /* FirebaseAuth in Frameworks */,
2631
);
2732
runOnlyForDeploymentPostprocessing = 0;
2833
};
@@ -65,6 +70,7 @@
6570
);
6671
name = FriendlyFlix;
6772
packageProductDependencies = (
73+
8854EC502CC6AC2700A09F27 /* FirebaseAuth */,
6874
);
6975
productName = FriendlyFlix;
7076
productReference = 88A9E6342CA834C600B3C4EF /* FriendlyFlix.app */;
@@ -94,6 +100,9 @@
94100
);
95101
mainGroup = 88A9E62B2CA834C600B3C4EF;
96102
minimizedProjectReferenceProxies = 1;
103+
packageReferences = (
104+
8854EC4F2CC6AC2700A09F27 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
105+
);
97106
preferredProjectObjectVersion = 77;
98107
productRefGroup = 88A9E6352CA834C600B3C4EF /* Products */;
99108
projectDirPath = "";
@@ -324,6 +333,25 @@
324333
defaultConfigurationName = Release;
325334
};
326335
/* End XCConfigurationList section */
336+
337+
/* Begin XCRemoteSwiftPackageReference section */
338+
8854EC4F2CC6AC2700A09F27 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
339+
isa = XCRemoteSwiftPackageReference;
340+
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
341+
requirement = {
342+
kind = upToNextMajorVersion;
343+
minimumVersion = 11.0.0;
344+
};
345+
};
346+
/* End XCRemoteSwiftPackageReference section */
347+
348+
/* Begin XCSwiftPackageProductDependency section */
349+
8854EC502CC6AC2700A09F27 /* FirebaseAuth */ = {
350+
isa = XCSwiftPackageProductDependency;
351+
package = 8854EC4F2CC6AC2700A09F27 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
352+
productName = FirebaseAuth;
353+
};
354+
/* End XCSwiftPackageProductDependency section */
327355
};
328356
rootObject = 88A9E62C2CA834C600B3C4EF /* Project object */;
329357
}

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/App/FriendlyFlixApp.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// limitations under the License.
1818

1919
import SwiftUI
20+
import Firebase
2021

2122
@main
2223
struct FriendlyFlixApp: App {
@@ -32,12 +33,13 @@ struct FriendlyFlixApp: App {
3233

3334
init () {
3435
loadRocketSimConnect()
36+
FirebaseApp.configure()
3537
}
3638

3739
var body: some Scene {
3840
WindowGroup {
3941
RootView()
40-
.environment(AuthenticationViewModel())
42+
.environment(AuthenticationService())
4143
}
4244
}
4345
}

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/App/RootView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import SwiftUI
2020

2121
struct RootView: View {
22-
@Environment(AuthenticationViewModel.self) private var authenticationViewModel
22+
@Environment(AuthenticationService.self) private var authenticationViewModel
2323

2424
var body: some View {
2525
@Bindable var authenticationViewModel = authenticationViewModel
@@ -48,5 +48,5 @@ struct RootView: View {
4848

4949
#Preview {
5050
RootView()
51-
.environment(AuthenticationViewModel())
51+
.environment(AuthenticationService())
5252
}

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/Features/Authentication/AccountScreen.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,27 @@ import SwiftUI
2020

2121
struct AccountScreen: View {
2222
@Environment(\.dismiss) var dismiss
23-
@Environment(AuthenticationViewModel.self) var authenticationViewModel
23+
@Environment(AuthenticationService.self) var authenticationService
2424

25+
private var displayName: String {
26+
authenticationService.user?.displayName ?? "(not set)"
27+
}
28+
29+
private var email: String {
30+
authenticationService.user?.email ?? ""
31+
}
32+
33+
private func signOut() {
34+
do {
35+
try authenticationService.signOut()
36+
dismiss()
37+
}
38+
catch {
39+
}
40+
}
41+
}
42+
43+
extension AccountScreen {
2544
var body: some View {
2645
NavigationStack {
2746
List {
@@ -32,8 +51,8 @@ struct AccountScreen: View {
3251
.scaledToFit()
3352
.frame(height: 48)
3453
VStack(alignment: .leading) {
35-
Text("Peter Friese")
36-
54+
Text(displayName)
55+
Text(email)
3756
}
3857
}
3958
}
@@ -45,10 +64,7 @@ struct AccountScreen: View {
4564
}
4665

4766
Section {
48-
Button(action: {
49-
authenticationViewModel.signOut()
50-
dismiss()
51-
}) {
67+
Button(action: signOut) {
5268
Text("Sign out")
5369
}
5470
}

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/Features/Authentication/AuthenticationScreen.swift

Lines changed: 44 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,32 @@
1919
import SwiftUI
2020
import AuthenticationServices
2121

22-
enum AuthenticationState {
23-
case unauthenticated
24-
case authenticating
25-
case authenticated
22+
private enum FocusableField: Hashable {
23+
case email
24+
case password
25+
case confirmPassword
2626
}
2727

28-
enum AuthenticationFlow {
28+
private enum AuthenticationFlow {
2929
case login
3030
case signUp
3131
}
3232

33-
@Observable
34-
class AuthenticationViewModel {
35-
var presentingAuthenticationDialog = false
36-
var presentingAccountDialog = false
33+
struct AuthenticationScreen: View {
34+
@Environment(AuthenticationService.self) var authenticationService
35+
@Environment(\.colorScheme) private var colorScheme
36+
@Environment(\.dismiss) private var dismiss
3737

38-
var email = ""
39-
var password = ""
40-
var confirmPassword = ""
38+
@State private var email = ""
39+
@State private var password = ""
40+
@State private var confirmPassword = ""
4141

42-
var flow: AuthenticationFlow = .login
42+
@State private var flow: AuthenticationFlow = .login
4343

44-
var authenticationState: AuthenticationState = .unauthenticated
45-
var errorMessage = ""
46-
var displayName = ""
44+
@State private var errorMessage = ""
45+
@State private var displayName = ""
4746

48-
var isValid: Bool {
47+
private var isValid: Bool {
4948
return if flow == .login {
5049
!email.isEmpty && !password.isEmpty
5150
}
@@ -54,53 +53,39 @@ class AuthenticationViewModel {
5453
}
5554
}
5655

57-
func switchFlow() {
56+
private func switchFlow() {
5857
flow = flow == .login ? .signUp : .login
5958
errorMessage = ""
6059
}
6160

62-
func signOut() {
63-
authenticationState = .unauthenticated
64-
}
65-
}
66-
67-
private enum FocusableField: Hashable {
68-
case email
69-
case password
70-
case confirmPassword
71-
}
72-
73-
struct AuthenticationScreen: View {
74-
@Environment(AuthenticationViewModel.self) var viewModel
75-
@Environment(\.colorScheme) private var colorScheme
76-
@Environment(\.dismiss) private var dismiss
77-
7861
@FocusState private var focus: FocusableField?
7962

8063
private func signInWithEmailPassword() {
81-
if viewModel.authenticationState == .authenticated {
82-
viewModel.authenticationState = .unauthenticated
83-
dismiss()
84-
}
85-
else {
86-
viewModel.authenticationState = .authenticated
87-
dismiss()
64+
Task {
65+
do {
66+
try await authenticationService.signInWithEmailPassword(email: email, password: password)
67+
dismiss()
68+
} catch {
69+
print(error.localizedDescription)
70+
errorMessage = error.localizedDescription
71+
}
8872
}
8973
}
9074

9175
private func signUpWithEmailPassword() {
92-
if viewModel.authenticationState == .authenticated {
93-
viewModel.authenticationState = .unauthenticated
76+
if authenticationService.authenticationState == .authenticated {
77+
authenticationService.authenticationState = .unauthenticated
9478
dismiss()
9579
}
9680
else {
97-
viewModel.authenticationState = .authenticated
81+
authenticationService.authenticationState = .authenticated
9882
dismiss()
9983
}
10084
}
85+
}
10186

87+
extension AuthenticationScreen {
10288
var body: some View {
103-
@Bindable var viewModel = viewModel
10489
VStack {
10590
// Image("login")
10691
// .resizable()
@@ -114,7 +99,7 @@ struct AuthenticationScreen: View {
11499

115100
HStack {
116101
Image(systemName: "at")
117-
TextField("Email", text: $viewModel.email)
102+
TextField("Email", text: $email)
118103
.textInputAutocapitalization(.never)
119104
.disableAutocorrection(true)
120105
.focused($focus, equals: .email)
@@ -129,7 +114,7 @@ struct AuthenticationScreen: View {
129114

130115
HStack {
131116
Image(systemName: "lock")
132-
SecureField("Password", text: $viewModel.password)
117+
SecureField("Password", text: $password)
133118
.focused($focus, equals: .password)
134119
.submitLabel(.go)
135120
.onSubmit {
@@ -140,10 +125,10 @@ struct AuthenticationScreen: View {
140125
.background(Divider(), alignment: .bottom)
141126
.padding(.bottom, 8)
142127

143-
if viewModel.flow == .signUp {
128+
if flow == .signUp {
144129
HStack {
145130
Image(systemName: "lock")
146-
SecureField("Confirm password", text: $viewModel.confirmPassword)
131+
SecureField("Confirm password", text: $confirmPassword)
147132
.focused($focus, equals: .confirmPassword)
148133
.submitLabel(.go)
149134
.onSubmit {
@@ -155,16 +140,16 @@ struct AuthenticationScreen: View {
155140
.padding(.bottom, 8)
156141
}
157142

158-
if !viewModel.errorMessage.isEmpty {
143+
if !errorMessage.isEmpty {
159144
VStack {
160-
Text(viewModel.errorMessage)
145+
Text(errorMessage)
161146
.foregroundColor(Color(UIColor.systemRed))
162147
}
163148
}
164149

165150
Button(action: signInWithEmailPassword) {
166-
if viewModel.authenticationState != .authenticating {
167-
Text(viewModel.flow == .login ? "Log in with password" : "Sign up")
151+
if authenticationService.authenticationState != .authenticating {
152+
Text(flow == .login ? "Log in with password" : "Sign up")
168153
.padding(.vertical, 8)
169154
.frame(maxWidth: .infinity)
170155
}
@@ -175,7 +160,7 @@ struct AuthenticationScreen: View {
175160
.frame(maxWidth: .infinity)
176161
}
177162
}
178-
.disabled(!viewModel.isValid)
163+
.disabled(!isValid)
179164
.frame(maxWidth: .infinity)
180165
.buttonStyle(.borderedProminent)
181166

@@ -185,7 +170,7 @@ struct AuthenticationScreen: View {
185170
VStack { Divider() }
186171
}
187172

188-
if viewModel.flow == .login {
173+
if flow == .login {
189174
SignInWithAppleButton(.signIn) { request in
190175
} onCompletion: { result in
191176
}
@@ -203,13 +188,13 @@ struct AuthenticationScreen: View {
203188
}
204189

205190
HStack {
206-
Text(viewModel.flow == .login ? "Don't have an account yet?" : "Already have an account?")
191+
Text(flow == .login ? "Don't have an account yet?" : "Already have an account?")
207192
Button(action: {
208193
withAnimation {
209-
viewModel.switchFlow()
194+
switchFlow()
210195
}
211196
}) {
212-
Text(viewModel.flow == .signUp ? "Log in" : "Sign up")
197+
Text(flow == .signUp ? "Log in" : "Sign up")
213198
.fontWeight(.semibold)
214199
.foregroundColor(.blue)
215200
}
@@ -223,5 +208,5 @@ struct AuthenticationScreen: View {
223208

224209
#Preview {
225210
AuthenticationScreen()
226-
.environment(AuthenticationViewModel())
211+
.environment(AuthenticationService())
227212
}

0 commit comments

Comments
 (0)