Просмотр исходного кода

Create button on Verification View to fetch age verification signal. (#461)

Brianna Morales 1 год назад
Родитель
Сommit
c54b610c4e

+ 4 - 0
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 = "<group>"; };
 		641495162C405E3600C9A613 /* VerificationLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationLoader.swift; sourceTree = "<group>"; };
 		6499D2292C4B2F4200825B30 /* Verification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Verification.swift; sourceTree = "<group>"; };
+		6499D22B2C4B3B1500825B30 /* AgeVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeVerificationView.swift; sourceTree = "<group>"; };
 		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 = "<group>"; };
 		7345AD042703D9470020AFB1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -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 */,

+ 11 - 11
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()
-      }
+      )
     }
   }
 }

+ 37 - 0
Samples/Swift/DaysUntilBirthday/Shared/ViewModels/VerifiedAgeViewModel.swift

@@ -24,6 +24,15 @@ 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
 
   /// Minimum time to expiration for a restored access token (10 minutes).
   let kMinimumRestoredAccessTokenTimeToExpire: TimeInterval = 600.0
@@ -32,9 +41,20 @@ final class VerifiedAgeViewModel: ObservableObject {
     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.
@@ -51,6 +71,23 @@ final class VerifiedAgeViewModel: ObservableObject {
       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 {

+ 42 - 0
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()
+          }
+        }
+      }
+    }
+  }
+}

+ 43 - 23
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: