diff --git a/Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj b/Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj index 365e583d..f1f0664b 100644 --- a/Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj +++ b/Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 641495152C405E1400C9A613 /* VerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641495142C405E1400C9A613 /* VerificationView.swift */; }; 641495172C405E3600C9A613 /* VerificationLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641495162C405E3600C9A613 /* VerificationLoader.swift */; }; 6499D22A2C4B2F4200825B30 /* Verification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6499D2292C4B2F4200825B30 /* Verification.swift */; }; + 6499D22C2C4B3B1500825B30 /* AgeVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6499D22B2C4B3B1500825B30 /* AgeVerificationView.swift */; }; 7345AD032703D9470020AFB1 /* DaysUntilBirthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345AD022703D9470020AFB1 /* DaysUntilBirthday.swift */; }; 7345AD052703D9470020AFB1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345AD042703D9470020AFB1 /* ContentView.swift */; }; 7345AD072703D9480020AFB1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7345AD062703D9480020AFB1 /* Assets.xcassets */; }; @@ -61,6 +62,7 @@ 641495142C405E1400C9A613 /* VerificationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationView.swift; sourceTree = ""; }; 641495162C405E3600C9A613 /* VerificationLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationLoader.swift; sourceTree = ""; }; 6499D2292C4B2F4200825B30 /* Verification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Verification.swift; sourceTree = ""; }; + 6499D22B2C4B3B1500825B30 /* AgeVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeVerificationView.swift; sourceTree = ""; }; 7345ACFF2703D9470020AFB1 /* DaysUntilBirthday (iOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DaysUntilBirthday (iOS).app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7345AD022703D9470020AFB1 /* DaysUntilBirthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaysUntilBirthday.swift; sourceTree = ""; }; 7345AD042703D9470020AFB1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -211,6 +213,7 @@ children = ( 7345AD042703D9470020AFB1 /* ContentView.swift */, 739FCC45270E467600C92042 /* BirthdayView.swift */, + 6499D22B2C4B3B1500825B30 /* AgeVerificationView.swift */, 7345AD112703D9C30020AFB1 /* SignInView.swift */, 641495142C405E1400C9A613 /* VerificationView.swift */, 7345AD142703D9C30020AFB1 /* UserProfileImageView.swift */, @@ -388,6 +391,7 @@ files = ( 6499D22A2C4B2F4200825B30 /* Verification.swift in Sources */, 739FCC48270E659A00C92042 /* Birthday.swift in Sources */, + 6499D22C2C4B3B1500825B30 /* AgeVerificationView.swift in Sources */, 739FCC46270E467600C92042 /* BirthdayView.swift in Sources */, 7345AD1B2703D9C30020AFB1 /* SignInView.swift in Sources */, 7345AD212703D9C30020AFB1 /* GoogleSignInAuthenticator.swift in Sources */, diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/VerificationLoader.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/VerificationLoader.swift index b75fdec2..380dce36 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/VerificationLoader.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/VerificationLoader.swift @@ -123,19 +123,19 @@ final class VerificationLoader: ObservableObject { func fetchAgeVerificationSignal(verifyResult: GIDVerifiedAccountDetailResult, completion: @escaping () -> Void) { self.verificationPublisher(verifyResult: verifyResult) { publisher in - self.cancellable = publisher.sink { sinkCompletion in - switch sinkCompletion { - case .finished: - break - case .failure(let error): - self.verification = Verification.noVerificationSignal - print("Error retrieving age verification: \(error)") + self.cancellable = publisher.sink( + receiveCompletion: { sinkCompletion in + if case .failure(let error) = sinkCompletion { + self.verification = Verification.noVerificationSignal + print("Error retrieving age verification: \(error)") + completion() + } + }, + receiveValue: { verification in + self.verification = verification completion() } - } receiveValue: { verification in - self.verification = verification - completion() - } + ) } } } diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/VerifiedAgeViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/VerifiedAgeViewModel.swift index 44315446..79099e3c 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/VerifiedAgeViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/VerifiedAgeViewModel.swift @@ -24,20 +24,57 @@ final class VerifiedAgeViewModel: ObservableObject { /// The user's account verification status. /// - note: This will publish updates when its value changes. @Published var verificationState: VerificationState + /// The age verification signal telling whether the user's age is over 18 or pending. + /// - note: This will publish updates when its value changes. + @Published var ageVerificationSignal: AgeVerificationSignal + /// Indicates whether the view to display the user's age is over 18 should be shown. + /// - note: This will publish updates when its value changes. + @Published var isShowingAgeVerificationSignal = false + /// Indicates whether an alert should be displayed to inform the user that they are not verified as over 18. + /// - note: This will publish updates when its value changes. + @Published var isShowingAgeVerificationAlert = false private lazy var loader: VerificationLoader = { return VerificationLoader(verifiedViewAgeModel: self) }() + /// An enumeration representing possible states of an age verification signal. + enum AgeVerificationSignal: String { + /// The user's age has been verified to be over 18. + case ageOver18Standard = "AGE_OVER_18_STANDARD" + /// The user's age verification is pending. + case agePending = "AGE_PENDING" + /// Indicates there was no age verification signal found. + case noAgeVerificationSignal = "Signal Unavailable" + } + /// Creates an instance of this view model. init() { self.verificationState = .unverified + self.ageVerificationSignal = .noAgeVerificationSignal } /// Verifies the user's age is over 18. func verifyUserAgeOver18() { loader.verifyUserAgeOver18() } + + /// Fetches the age verification signal representing whether the user's age is over 18 or pending. + func fetchAgeVerificationSignal(verifyResult: GIDVerifiedAccountDetailResult) { + loader.fetchAgeVerificationSignal(verifyResult: verifyResult) { + let signal = self.loader.verification?.signal ?? "" + self.ageVerificationSignal = AgeVerificationSignal(rawValue: signal) ?? + .noAgeVerificationSignal + + if (self.ageVerificationSignal == .ageOver18Standard) { + self.isShowingAgeVerificationSignal = true + self.isShowingAgeVerificationAlert = false + } else { + self.isShowingAgeVerificationSignal = false + self.isShowingAgeVerificationAlert = true + } + } + } } extension VerifiedAgeViewModel { diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Views/AgeVerificationView.swift b/Samples/Swift/DaysUntilBirthday/Shared/Views/AgeVerificationView.swift new file mode 100644 index 00000000..2b3c0be8 --- /dev/null +++ b/Samples/Swift/DaysUntilBirthday/Shared/Views/AgeVerificationView.swift @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI +import GoogleSignIn + +struct AgeVerificationResultView: View { + /// The age verification signal telling whether the user's age is over 18 or pending. + let ageVerificationSignal: String + + @Environment(\.presentationMode) var presentationMode + + var body: some View { + NavigationView { + VStack { + Text("Age Verification Signal: \(ageVerificationSignal)") + Spacer() + } + .navigationTitle("User is verified over 18!") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Close") { + presentationMode.wrappedValue.dismiss() + } + } + } + } + } +} diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Views/VerificationView.swift b/Samples/Swift/DaysUntilBirthday/Shared/Views/VerificationView.swift index 7c8024f3..fe072b11 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Views/VerificationView.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Views/VerificationView.swift @@ -23,33 +23,53 @@ struct VerificationView: View { var body: some View { switch verifiedAgeViewModel.verificationState { case .verified(let result): - VStack(alignment:.leading) { - Text("List of result object properties:") - .font(.headline) + Button("Fetch Age Verification Signal") { + verifiedAgeViewModel.fetchAgeVerificationSignal(verifyResult: result) + } + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 30) + .stroke(Color.gray) + ) + .padding(.bottom) + if #available(iOS 15, *) { + VStack(alignment:.leading) { + Text("List of result object properties:") + .font(.headline) - HStack(alignment: .top) { - Text("Access Token:") - Text(result.accessToken?.tokenString ?? "Not available") - } - HStack(alignment: .top) { - Text("Refresh Token:") - Text(result.refreshToken?.tokenString ?? "Not available") + HStack(alignment: .top) { + Text("Access Token:") + Text(result.accessToken?.tokenString ?? "Not available") + } + HStack(alignment: .top) { + Text("Refresh Token:") + Text(result.refreshToken?.tokenString ?? "Not available") + } + HStack { + Text("Expiration:") + if let expirationDate = result.accessToken?.expirationDate { + Text(formatDateWithDateFormatter(expirationDate)) + } else { + Text("Not available") + } + } + Spacer() } - HStack { - Text("Expiration:") - if let expirationDate = result.accessToken?.expirationDate { - Text(formatDateWithDateFormatter(expirationDate)) - } else { - Text("Not available") + .navigationTitle("Verified Account!") + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button(NSLocalizedString("Refresh", comment: "Refresh button"), + action:{refresh(results: result)}) } } - Spacer() - } - .navigationTitle("Verified Account!") - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(NSLocalizedString("Refresh", comment: "Refresh button"), - action:{refresh(results: result)}) + .sheet(isPresented: $verifiedAgeViewModel.isShowingAgeVerificationSignal) { + AgeVerificationResultView(ageVerificationSignal: verifiedAgeViewModel.ageVerificationSignal.rawValue) + } + .alert("Oh no! User is not verified over 18.", + isPresented: $verifiedAgeViewModel.isShowingAgeVerificationAlert) { + Button("OK", role: .cancel) { } + } message: { + Text("Age Verification Signal: \(verifiedAgeViewModel.ageVerificationSignal.rawValue)") } } case .unverified: