aboutsummaryrefslogtreecommitdiffhomepage
path: root/AuthSamples
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /AuthSamples
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'AuthSamples')
-rw-r--r--AuthSamples/ApiTests/FirebearApiTests.m542
-rw-r--r--AuthSamples/ApiTests/Info.plist22
-rw-r--r--AuthSamples/EarlGreyTests/FirebearEarlGreyTests.m193
-rw-r--r--AuthSamples/EarlGreyTests/Info.plist22
-rw-r--r--AuthSamples/Podfile40
-rw-r--r--AuthSamples/README.md65
-rw-r--r--AuthSamples/Sample/ApplicationDelegate.h48
-rw-r--r--AuthSamples/Sample/ApplicationDelegate.m80
-rw-r--r--AuthSamples/Sample/ApplicationTemplate.plist88
-rw-r--r--AuthSamples/Sample/AuthCredentialsTemplate.h53
-rw-r--r--AuthSamples/Sample/AuthProviders.h74
-rw-r--r--AuthSamples/Sample/AuthProviders.m40
-rw-r--r--AuthSamples/Sample/CustomTokenDataEntryViewController.h55
-rw-r--r--AuthSamples/Sample/CustomTokenDataEntryViewController.m148
-rw-r--r--AuthSamples/Sample/FacebookAuthProvider.h29
-rw-r--r--AuthSamples/Sample/FacebookAuthProvider.m78
-rw-r--r--AuthSamples/Sample/GoogleAuthProvider.h29
-rw-r--r--AuthSamples/Sample/GoogleAuthProvider.m132
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/Contents.json177
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp-1.pngbin0 -> 169 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp.pngbin0 -> 169 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_29in40dp-1.pngbin0 -> 194 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_38in50dp.pngbin0 -> 222 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_42in57dp.pngbin0 -> 251 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_53in72dp.pngbin0 -> 303 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_56in76dp.pngbin0 -> 315 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp-1.pngbin0 -> 248 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp.pngbin0 -> 248 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-1.pngbin0 -> 317 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-2.pngbin0 -> 317 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_38in50dp.pngbin0 -> 390 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_42in57dp.pngbin0 -> 424 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_44in60dp.pngbin0 -> 438 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_53in72dp.pngbin0 -> 540 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_56in76dp.pngbin0 -> 573 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_21in29dp.pngbin0 -> 330 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_29in40dp-1.pngbin0 -> 433 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_44in60dp.pngbin0 -> 661 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/Contents.json6
-rw-r--r--AuthSamples/Sample/Images.xcassets/close.imageset/Contents.json23
-rw-r--r--AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_1x_ios_24dp.pngbin0 -> 164 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_2x_ios_24dp.pngbin0 -> 235 bytes
-rw-r--r--AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_3x_ios_24dp.pngbin0 -> 309 bytes
-rw-r--r--AuthSamples/Sample/MainViewController.h85
-rw-r--r--AuthSamples/Sample/MainViewController.m2496
-rw-r--r--AuthSamples/Sample/MainViewController.xib330
-rw-r--r--AuthSamples/Sample/Sample.entitlements.dev10
-rw-r--r--AuthSamples/Sample/Sample.entitlements.enterprise10
-rw-r--r--AuthSamples/Sample/SettingsViewController.h37
-rw-r--r--AuthSamples/Sample/SettingsViewController.m381
-rw-r--r--AuthSamples/Sample/SettingsViewController.xib55
-rw-r--r--AuthSamples/Sample/StaticContentTableViewManager.h257
-rw-r--r--AuthSamples/Sample/StaticContentTableViewManager.m231
-rw-r--r--AuthSamples/Sample/Strings/ar.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ca.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/cs.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/da.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/de.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/el.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/en.lproj/FirebaseAuthUI.strings2
-rw-r--r--AuthSamples/Sample/Strings/en_GB.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/es.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/es_MX.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/fi.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/fr.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/he.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/hr.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/hu.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/id.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/it.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ja.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ko.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ms.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/nb.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/nl.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/pl.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/pt.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/pt_BR.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/pt_PT.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ro.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/ru.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/sk.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/sv.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/th.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/tr.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/uk.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/vi.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/zh_CN.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/Strings/zh_TW.lproj/FirebearSample.strings1
-rw-r--r--AuthSamples/Sample/UIViewController+Alerts.h91
-rw-r--r--AuthSamples/Sample/UIViewController+Alerts.m346
-rw-r--r--AuthSamples/Sample/UserInfoViewController.h55
-rw-r--r--AuthSamples/Sample/UserInfoViewController.m79
-rw-r--r--AuthSamples/Sample/UserInfoViewController.xib55
-rw-r--r--AuthSamples/Sample/UserTableViewCell.h58
-rw-r--r--AuthSamples/Sample/UserTableViewCell.m55
-rw-r--r--AuthSamples/Sample/main.m23
-rw-r--r--AuthSamples/Samples.xcodeproj/project.pbxproj1806
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme119
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/ApiTests.xcscheme56
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/EarlGreyTests.xcscheme56
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/FirebaseAuthUnitTests.xcscheme56
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme111
-rw-r--r--AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/SwiftBear.xcscheme91
-rw-r--r--AuthSamples/SwiftSample/AppDelegate.swift62
-rw-r--r--AuthSamples/SwiftSample/AuthCredentialsTemplate.swift42
-rw-r--r--AuthSamples/SwiftSample/InfoTemplate.plist79
-rw-r--r--AuthSamples/SwiftSample/LaunchScreen.storyboard27
-rw-r--r--AuthSamples/SwiftSample/Main.storyboard186
-rw-r--r--AuthSamples/SwiftSample/ViewController.swift521
110 files changed, 9847 insertions, 0 deletions
diff --git a/AuthSamples/ApiTests/FirebearApiTests.m b/AuthSamples/ApiTests/FirebearApiTests.m
new file mode 100644
index 0000000..98c4769
--- /dev/null
+++ b/AuthSamples/ApiTests/FirebearApiTests.m
@@ -0,0 +1,542 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+
+#import "FIRApp.h"
+#import "FirebaseAuth.h"
+#import "../Sample/AuthCredentials.h"
+
+#ifdef NO_NETWORK
+#import "ioReplayer/IORManager.h"
+#import "ioReplayer/IORTestCase.h"
+#endif
+
+#import <GTMSessionFetcher/GTMSessionFetcher.h>
+#import <GTMSessionFetcher/GTMSessionFetcherService.h>
+
+/** Facebook app access token that will be used for Facebook Graph API, which is different from
+ * account access token.
+ */
+static NSString *const kAppAccessToken = KAPP_ACCESS_TOKEN;
+
+/** The user name string for BYOAuth testing account. */
+static NSString *const kBYOAuthTestingAccountUserName = @"John GoogleSpeed";
+
+/** The url for obtaining a valid custom token string used to test BYOAuth. */
+static NSString *const kCustomTokenUrl = @"https://fb-sa-1211.appspot.com/token";
+
+/** Facebook app ID that will be used for Facebook Graph API. */
+static NSString *const kFacebookAppID = KFACEBOOK_APP_ID;
+
+static NSString *const kFacebookGraphApiAuthority = @"graph.facebook.com";
+
+static NSString *const kFacebookTestAccountName = @"MichaelTest";
+
+static NSString *const kGoogleTestAccountName = @"John Test";
+
+/** The invalid custom token string for testing BYOAuth. */
+static NSString *const kInvalidCustomToken = @"invalid token.";
+
+/** The testing email address for testCreateAccountWithEmailAndPassword. */
+static NSString *const kTestingEmailToCreateUser = @"abc@xyz.com";
+
+/** The testing email address for testSignInExistingUserWithEmailAndPassword. */
+static NSString *const kExistingTestingEmailToSignIn = @"456@abc.com";
+
+/** Error message for invalid custom token sign in. */
+NSString *kInvalidTokenErrorMessage =
+ @"The custom token format is incorrect. Please check the documentation.";
+
+/** Cliend Id for Google sign in project "fb-sa-upgraded". Please be sync with the CLIENT_ID in
+ * Firebear/Sample/GoogleService-Info.plist. */
+NSString *kGoogleCliendId = KGOOGLE_CLIENT_ID;
+
+/** Refresh token of Google test account to exchange for access token. Refresh token never expires
+ * unless user revokes it. If this refresh token expires, tests in record mode will fail and this
+ * token needs to be updated. More details at
+ * http://g3doc/company/teams/user-testing/identity/firebear/faq. */
+NSString *kGoogleTestAccountRefreshToken = KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN;
+
+static NSTimeInterval const kExpectationsTimeout = 30;
+
+#ifdef NO_NETWORK
+#define SKIP_IF_ON_MOBILE_HARNESS \
+ if ([ITUIOSTestUtil isOnMobileHarness]) { \
+ NSLog(@"Skipping '%@' on mobile harness", NSStringFromSelector(_cmd)); \
+ return; \
+ }
+#else
+#define SKIP_IF_ON_MOBILE_HARNESS
+#endif
+
+#ifdef NO_NETWORK
+@interface ApiTests : IORTestCase
+#else
+@interface ApiTests : XCTestCase
+#endif
+@end
+
+@implementation ApiTests
+
+/** To reset the app so that each test sees the app in a clean state. */
+- (void)setUp {
+ [super setUp];
+ [self signOut];
+}
+
+#pragma mark - Tests
+
+/**
+ * This test runs in replay mode by default. To run in a different mode follow the instructions
+ * below.
+ *
+ * Blaze: --test_arg=\'--networkReplayMode=(replay|record|disabled|observe)\'
+ *
+ * Xcode:
+ * Update the following flag in the xcscheme.
+ * --networkReplayMode=(replay|record|disabled|observe)
+ */
+- (void)testCreateAccountWithEmailAndPassword {
+ SKIP_IF_ON_MOBILE_HARNESS
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Created account with email and password."];
+ [auth createUserWithEmail:kTestingEmailToCreateUser
+ password:@"password"
+ completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"createUserWithEmail has error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in creating account. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+
+ XCTAssertEqualObjects(auth.currentUser.email, kTestingEmailToCreateUser);
+
+ // Clean up the created Firebase user for future runs.
+ [self deleteCurrentFirebaseUser];
+}
+
+- (void)testLinkAnonymousAccountToFacebookAccount {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ [self signInAnonymously];
+
+ NSDictionary *userInfoDict = [self createFacebookTestingAccount];
+ NSString *facebookAccessToken = userInfoDict[@"access_token"];
+ NSLog(@"Facebook testing account access token is: %@", facebookAccessToken);
+ NSString *facebookAccountId = userInfoDict[@"id"];
+ NSLog(@"Facebook testing account id is: %@", facebookAccountId);
+
+ FIRAuthCredential *credential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:facebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook linking finished."];
+ [auth.currentUser linkWithCredential:credential
+ completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"Link to Facebok error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in linking to Facebook. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ NSArray<id<FIRUserInfo>> *providerData = auth.currentUser.providerData;
+ XCTAssertEqual([providerData count], 1);
+ XCTAssertEqualObjects([providerData[0] providerID], @"facebook.com");
+
+ // Clean up the created Firebase/Facebook user for future runs.
+ [self deleteCurrentFirebaseUser];
+ [self deleteFacebookTestingAccountbyId:facebookAccountId];
+}
+
+- (void)testSignInAnonymously {
+ [self signInAnonymously];
+ XCTAssertTrue([FIRAuth auth].currentUser.anonymous);
+ [self deleteCurrentFirebaseUser];
+}
+
+- (void)testSignInExistingUserWithEmailAndPassword {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Signed in existing account with email and password."];
+ [auth signInWithEmail:kExistingTestingEmailToSignIn
+ password:@"password"
+ completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"Signing in existing account has error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in signing in existing account. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+
+ XCTAssertEqualObjects(auth.currentUser.email, kExistingTestingEmailToSignIn);
+}
+
+- (void)testSignInWithValidBYOAuthToken {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ if (!customToken) {
+ XCTFail(@"There was an error retrieving the custom token: %@", error);
+ }
+ NSLog(@"The valid token is: %@", customToken);
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"BYOAuthToken sign-in finished."];
+
+ [auth signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ NSLog(@"Valid token sign in error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in BYOAuthToken sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+
+ XCTAssertEqualObjects(auth.currentUser.displayName, kBYOAuthTestingAccountUserName);
+}
+
+- (void)testSignInWithInvalidBYOAuthToken {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Invalid BYOAuthToken sign-in finished."];
+
+ [auth signInWithCustomToken:kInvalidCustomToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+
+ XCTAssertEqualObjects(error.localizedDescription, kInvalidTokenErrorMessage);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in BYOAuthToken sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+}
+
+- (void)testSignInWithFaceboook {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+
+ NSDictionary *userInfoDict = [self createFacebookTestingAccount];
+ NSString *facebookAccessToken = userInfoDict[@"access_token"];
+ NSLog(@"Facebook testing account access token is: %@", facebookAccessToken);
+ NSString *facebookAccountId = userInfoDict[@"id"];
+ NSLog(@"Facebook testing account id is: %@", facebookAccountId);
+
+ FIRAuthCredential *credential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:facebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook sign-in finished."];
+
+ [auth signInWithCredential:credential
+ completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"Facebook sign in error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in Facebook sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ XCTAssertEqualObjects(auth.currentUser.displayName, kFacebookTestAccountName);
+
+ // Clean up the created Firebase/Facebook user for future runs.
+ [self deleteCurrentFirebaseUser];
+ [self deleteFacebookTestingAccountbyId:facebookAccountId];
+}
+
+- (void)testSignInWithGoogle {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+ NSDictionary *userInfoDict = [self getGoogleAccessToken];
+ NSString *googleAccessToken = userInfoDict[@"access_token"];
+ NSString *googleIdToken = userInfoDict[@"id_token"];
+ FIRAuthCredential *credential =
+ [FIRGoogleAuthProvider credentialWithIDToken:googleIdToken accessToken:googleAccessToken];
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Signing in with Google finished."];
+ [auth signInWithCredential:credential
+ completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"Signing in with Google had error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in Signing in with Google. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ XCTAssertEqualObjects(auth.currentUser.displayName, kGoogleTestAccountName);
+
+ // Clean up the created Firebase/Facebook user for future runs.
+ [self deleteCurrentFirebaseUser];
+}
+
+#pragma mark - Helpers
+
+/** Sign out current account. */
+- (void)signOut {
+ NSError *signOutError;
+ BOOL status = [[FIRAuth auth] signOut:&signOutError];
+
+ // Just log the error because we don't want to fail the test if signing out
+ // fails.
+ if (!status) {
+ NSLog(@"Error signing out: %@", signOutError);
+ }
+}
+
+/** Creates a Facebook testing account using Facebook Graph API and return a dictionary that
+ * constains "id", "access_token", "login_url", "email" and "password" of the created account. More
+ * details at http://g3doc/company/teams/user-testing/identity/firebear/faq.
+ */
+- (NSDictionary *)createFacebookTestingAccount {
+ // Build the URL.
+ NSString *urltoCreateTestUser =
+ [NSString stringWithFormat:@"https://%@/%@/accounts/test-users", kFacebookGraphApiAuthority,
+ kFacebookAppID];
+ // Build the POST request.
+ NSString *bodyString =
+ [NSString stringWithFormat:@"installed=true&name=%@&permissions=read_stream&access_token=%@",
+ kFacebookTestAccountName, kAppAccessToken];
+ NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
+ GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
+ GTMSessionFetcher *fetcher = [service fetcherWithURLString:urltoCreateTestUser];
+ fetcher.bodyData = postData;
+ [fetcher setRequestValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Creating Facebook account finished."];
+ __block NSData *data = nil;
+ [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
+ if (error) {
+ NSLog(@"Creating Facebook account finished with error: %@", error);
+ return;
+ }
+ data = receivedData;
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in creating Facebook account. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ NSString *userInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSLog(@"The info of created Facebook testing account is: %@", userInfo);
+ // Parses the access token from the JSON data.
+ NSDictionary *userInfoDict =
+ [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
+ return userInfoDict;
+}
+
+/** Clean up the created user for tests' future runs. */
+- (void)deleteCurrentFirebaseUser {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ NSLog(@"Could not obtain auth object.");
+ }
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Delete current user finished."];
+ [auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ XCTFail(@"Failed to delete user. Error: %@.", error);
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in deleting user. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+}
+
+- (void)signInAnonymously {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ XCTFail(@"Could not obtain auth object.");
+ }
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Anonymousy sign-in finished."];
+ [auth signInAnonymouslyWithCompletion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ NSLog(@"Anonymousy sign in error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in anonymousy sign in. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+}
+
+/** Delete a Facebook testing account by account Id using Facebook Graph API. */
+- (void)deleteFacebookTestingAccountbyId:(NSString *)accountId {
+ // Build the URL.
+ NSString *urltoDeleteTestUser =
+ [NSString stringWithFormat:@"https://%@/%@", kFacebookGraphApiAuthority, accountId];
+
+ // Build the POST request.
+ NSString *bodyString =
+ [NSString stringWithFormat:@"method=delete&access_token=%@", kAppAccessToken];
+ NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
+ GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
+ GTMSessionFetcher *fetcher = [service fetcherWithURLString:urltoDeleteTestUser];
+ fetcher.bodyData = postData;
+ [fetcher setRequestValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Deleting Facebook account finished."];
+ [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
+ NSString *deleteResult =
+ [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
+ NSLog(@"The result of deleting Facebook account is: %@", deleteResult);
+ if (error) {
+ NSLog(@"Deleting Facebook account finished with error: %@", error);
+ }
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in deleting Facebook account. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+}
+
+/** Sends http request to Google OAuth2 token server to use refresh token to exchange for Google
+ * access token. Returns a dictionary that constains "access_token", "token_type", "expires_in" and
+ * "id_token".
+ */
+- (NSDictionary *)getGoogleAccessToken {
+ NSString *googleOauth2TokenServerUrl = @"https://www.googleapis.com/oauth2/v4/token";
+ NSString *bodyString =
+ [NSString stringWithFormat:@"client_id=%@&grant_type=refresh_token&refresh_token=%@",
+ kGoogleCliendId, kGoogleTestAccountRefreshToken];
+ NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
+ GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
+ GTMSessionFetcher *fetcher = [service fetcherWithURLString:googleOauth2TokenServerUrl];
+ fetcher.bodyData = postData;
+ [fetcher setRequestValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
+
+ XCTestExpectation *expectation =
+ [self expectationWithDescription:@"Exchanging Google account tokens finished."];
+ __block NSData *data = nil;
+ [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
+ if (error) {
+ NSLog(@"Exchanging Google account tokens finished with error: %@", error);
+ return;
+ }
+ data = receivedData;
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationsTimeout
+ handler:^(NSError *error) {
+ if (error != nil) {
+ XCTFail(@"Failed to wait for expectations "
+ @"in exchanging Google account tokens. Error: %@",
+ error.localizedDescription);
+ }
+ }];
+ NSString *userInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSLog(@"The info of exchanged result is: %@", userInfo);
+ NSDictionary *userInfoDict =
+ [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
+ return userInfoDict;
+}
+@end
diff --git a/AuthSamples/ApiTests/Info.plist b/AuthSamples/ApiTests/Info.plist
new file mode 100644
index 0000000..6c6c23c
--- /dev/null
+++ b/AuthSamples/ApiTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/AuthSamples/EarlGreyTests/FirebearEarlGreyTests.m b/AuthSamples/EarlGreyTests/FirebearEarlGreyTests.m
new file mode 100644
index 0000000..7f07e2a
--- /dev/null
+++ b/AuthSamples/EarlGreyTests/FirebearEarlGreyTests.m
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <EarlGrey/EarlGrey.h>
+#import <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+
+#import "FIRApp.h"
+#import "FirebaseAuth.h"
+
+#ifdef NO_NETWORK
+#import "ioReplayer/IORTestCase.h"
+#endif
+
+/** The url for obtaining a valid custom token string used to test BYOAuth. */
+static NSString *const kCustomTokenUrl = @"https://fb-sa-1211.appspot.com/token";
+
+/** The invalid custom token string for testing BYOAuth. */
+static NSString *const kInvalidCustomToken = @"invalid token.";
+
+/** The user name string for BYOAuth testing account. */
+static NSString *const kTestingAccountUserID = @"BYU_Test_User_ID";
+
+static CGFloat const kShortScrollDistance = 100;
+
+static NSTimeInterval const kWaitForElementTimeOut = 5;
+
+#ifdef NO_NETWORK
+@interface BasicUITest : IORTestCase
+#else
+@interface BasicUITest :XCTestCase
+#endif
+@end
+
+/** Convenience function for EarlGrey tests. */
+id<GREYMatcher> grey_scrollView(void) {
+ return [GREYMatchers matcherForKindOfClass:[UIScrollView class]];
+}
+
+@implementation BasicUITest
+
+/** To reset the app so that each test sees the app in a clean state. */
+- (void)setUp {
+ [super setUp];
+
+ [self signOut];
+
+ [[EarlGrey selectElementWithMatcher:grey_allOf(grey_scrollView(),
+ grey_kindOfClass([UITableView class]), nil)]
+ performAction:grey_scrollToContentEdge(kGREYContentEdgeTop)];
+}
+
+#pragma mark - Tests
+
+/**
+ * This test runs in replay mode by default. To run in a different mode
+ * follow the instructions below.
+ *
+ * Blaze:
+ * --test_arg=\'--networkReplayMode=(replay|record|disabled|observe)\'
+ *
+ * Xcode:
+ * Update the following flag in the xcscheme.
+ * --networkReplayMode=(replay|record|disabled|observe)
+ */
+- (void)testSignInExistingUser {
+ NSString *email = @"123@abc.com";
+ [[[EarlGrey selectElementWithMatcher:grey_allOf(grey_text(@"Sign in with Email/Password"),
+ grey_sufficientlyVisible(), nil)]
+ usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, kShortScrollDistance)
+ onElementWithMatcher:grey_allOf(grey_scrollView(), grey_kindOfClass([UITableView class]),
+ nil)] performAction:grey_tap()];
+
+ id<GREYMatcher> comfirmationButtonMatcher =
+ grey_allOf(grey_kindOfClass([UILabel class]), grey_accessibilityLabel(@"OK"), nil);
+
+ [[EarlGrey selectElementWithMatcher:
+ #warning TODO Add accessibilityIdentifiers for the elements.
+ grey_kindOfClass(NSClassFromString(@"_UIAlertControllerView"))]
+ performAction:grey_typeText(email)];
+
+ [[EarlGrey selectElementWithMatcher:comfirmationButtonMatcher] performAction:grey_tap()];
+
+ [[EarlGrey
+ selectElementWithMatcher:grey_kindOfClass(NSClassFromString(@"_UIAlertControllerView"))]
+ performAction:grey_typeText(@"password")];
+
+ [[EarlGrey selectElementWithMatcher:comfirmationButtonMatcher] performAction:grey_tap()];
+
+ [[[EarlGrey
+ selectElementWithMatcher:grey_allOf(grey_text(email), grey_sufficientlyVisible(), nil)]
+ usingSearchAction:grey_scrollInDirection(kGREYDirectionUp, kShortScrollDistance)
+ onElementWithMatcher:grey_allOf(grey_scrollView(), grey_kindOfClass([UITableView class]),
+ nil)] assertWithMatcher:grey_sufficientlyVisible()];
+}
+
+/** Test sign in with a valid BYOAuth token retrived from a remote server. */
+- (void)testSignInWithValidBYOAuthToken {
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ if (!customToken) {
+ GREYFail(@"There was an error retrieving the custom token: %@", error);
+ }
+
+ [[[EarlGrey selectElementWithMatcher:grey_allOf(grey_text(@"Sign In (BYOAuth)"),
+ grey_sufficientlyVisible(), nil)]
+ usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, kShortScrollDistance)
+ onElementWithMatcher:grey_allOf(grey_scrollView(), grey_kindOfClass([UITableView class]),
+ nil)] performAction:grey_tap()];
+
+ [[[EarlGrey selectElementWithMatcher:grey_kindOfClass([UITextView class])]
+ performAction:grey_replaceText(customToken)] assertWithMatcher:grey_text(customToken)];
+
+ [[EarlGrey selectElementWithMatcher:grey_text(@"Done")] performAction:grey_tap()];
+
+ [self waitForElementWithText:@"OK" withDelay:kWaitForElementTimeOut];
+
+ [[EarlGrey selectElementWithMatcher:grey_text(@"OK")] performAction:grey_tap()];
+
+ [[[EarlGrey
+ selectElementWithMatcher:grey_allOf(grey_text(kTestingAccountUserID),
+ grey_sufficientlyVisible(), nil)]
+ usingSearchAction:grey_scrollInDirection(kGREYDirectionUp,
+ kShortScrollDistance)
+ onElementWithMatcher:grey_allOf(grey_scrollView(),
+ grey_kindOfClass([UITableView class]),
+ nil)]
+ assertWithMatcher:grey_sufficientlyVisible()];
+}
+
+- (void)testSignInWithInvalidBYOAuthToken {
+ [[[EarlGrey selectElementWithMatcher:grey_allOf(grey_text(@"Sign In (BYOAuth)"),
+ grey_sufficientlyVisible(), nil)]
+ usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, kShortScrollDistance)
+ onElementWithMatcher:grey_allOf(grey_scrollView(), grey_kindOfClass([UITableView class]),
+ nil)] performAction:grey_tap()];
+
+ [[[EarlGrey selectElementWithMatcher:grey_kindOfClass([UITextView class])]
+ performAction:grey_replaceText(kInvalidCustomToken)]
+ assertWithMatcher:grey_text(kInvalidCustomToken)];
+
+ [[EarlGrey selectElementWithMatcher:grey_text(@"Done")] performAction:grey_tap()];
+
+ NSString *invalidTokenErrorMessage =
+ @"The custom token format is incorrect. Please check the documentation.";
+
+ [self waitForElementWithText:invalidTokenErrorMessage withDelay:kWaitForElementTimeOut];
+
+ [[EarlGrey selectElementWithMatcher:grey_text(@"OK")] performAction:grey_tap()];
+}
+
+#pragma mark - Helpers
+
+/** Sign out current account. */
+- (void)signOut {
+ NSError *signOutError;
+ BOOL status = [[FIRAuth auth] signOut:&signOutError];
+
+ // Just log the error because we don't want to fail the test if signing out fails.
+ if (!status) {
+ NSLog(@"Error signing out: %@", signOutError);
+ }
+}
+
+/** Wait for an element with text to appear. */
+- (void)waitForElementWithText:(NSString *)text withDelay:(NSTimeInterval)maxDelay {
+ GREYCondition *displayed =
+ [GREYCondition conditionWithName:@"Wait for element"
+ block:^BOOL {
+ NSError *error = nil;
+ [[EarlGrey selectElementWithMatcher:grey_text(text)]
+ assertWithMatcher:grey_sufficientlyVisible()
+ error:&error];
+ return !error;
+ }];
+ GREYAssertTrue([displayed waitWithTimeout:maxDelay], @"Failed to wait for element '%@'.", text);
+}
+@end
diff --git a/AuthSamples/EarlGreyTests/Info.plist b/AuthSamples/EarlGreyTests/Info.plist
new file mode 100644
index 0000000..6c6c23c
--- /dev/null
+++ b/AuthSamples/EarlGreyTests/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/AuthSamples/Podfile b/AuthSamples/Podfile
new file mode 100644
index 0000000..473fefc
--- /dev/null
+++ b/AuthSamples/Podfile
@@ -0,0 +1,40 @@
+use_frameworks!
+platform :ios, '8.0'
+
+target 'Sample' do
+ pod 'FirebaseDev/Auth', :path => '../'
+ pod 'FBSDKLoginKit'
+ pod 'GoogleSignIn'
+ # Lock to the 1.0.9 version of InstanceID since 1.0.10 added a dependency
+ # to FirebaseCore
+ pod 'FirebaseInstanceID', '1.0.9'
+end
+
+target 'SwiftBear' do
+ pod 'FirebaseDev/Auth', :path => '../'
+ pod 'GoogleSignIn'
+ pod 'FirebaseInstanceID', '1.0.9'
+end
+
+target 'ApiTests' do
+ pod 'FirebaseDev/Auth', :path => '../'
+ pod 'GoogleSignIn'
+ pod 'FirebaseInstanceID', '1.0.9'
+ pod 'GTMSessionFetcher'
+end
+
+target 'EarlGreyTests' do
+ pod 'FirebaseDev/Auth', :path => '../'
+ pod 'GoogleSignIn'
+ pod 'FirebaseInstanceID', '1.0.9'
+ pod 'EarlGrey'
+end
+
+target 'TestApp' do
+ pod 'FirebaseDev/Auth', :path => '../'
+end
+
+target 'FirebaseAuthUnitTests' do
+ pod 'FirebaseDev/Auth', :path => '../'
+ pod 'OCMock'
+end
diff --git a/AuthSamples/README.md b/AuthSamples/README.md
new file mode 100644
index 0000000..6117293
--- /dev/null
+++ b/AuthSamples/README.md
@@ -0,0 +1,65 @@
+# Firebase Auth Development
+
+This directory contains a set of samples and tests that integrate with
+FirebaseAuth.
+
+The Podfile specifies the dependencies and is used to construct an Xcode
+workspace consisting of the samples, modifiable FirebaseAuth library, and its
+dependencies.
+
+
+### Running Sample Application or Firebear Api Tests
+
+In order to run this application, you'll need to follow the following steps!
+
+#### GoogleService-Info.plist files
+
+You'll need valid `GoogleService-Info.plist` files for those samples. To get your own `GoogleService-Info.plist` files:
+1. Go to the [Firebase Console](https://console.firebase.google.com/)
+2. Create a new Firebase project, if you don't already have one
+3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.FirebaseExperimental1.dev`)
+4. Download the resulting `GoogleService-Info.plist` and place it in [Sample/GoogleService-Info.plist](Sample/GoogleService-Info.plist)
+
+#### GoogleService-Info_multi.plist files
+
+This feature is for advanced testing.
+1. The developer would need to get a GoogleService-Info.plist from a different iOS client (which can be in a different Firebase project)
+2. Save this plist file as GoogleService-Info_multi.plist in [Sample/GoogleService-Info_multi.plist](Sample/GoogleService-Info_multi.plist). This enables testing that FirebaseAuth continues to work after switching the Firebase App in the runtime.
+
+#### Application.plist file
+
+Please follow the instructions in [Sample/ApplicationTemplate.plist](Sample/ApplicationTemplate.plist) to generate the right Application.plist file
+
+#### Getting your own Credential files
+
+Please follow the instructions in [Sample/AuthCredentialsTemplate.h](Sample/AuthCredentialsTemplate.h) to generate the AuthCredentials.h file.
+
+
+### Running SwiftSample Application
+
+In order to run this application, you'll need to follow the following steps!
+
+#### GoogleService-Info.plist files
+
+You'll need valid `GoogleService-Info.plist` files for those samples. To get your own `GoogleService-Info.plist` files:
+1. Go to the [Firebase Console](https://console.firebase.google.com/)
+2. Create a new Firebase project, if you don't already have one
+3. For each sample app you want to test, create a new Firebase app with the sample app's bundle identifier (e.g. `com.google.SwiftBear`)
+4. Download the resulting `GoogleService-Info.plist` and place it in [SwiftSample/GoogleService-Info.plist](SwiftSample/GoogleService-Info.plist)
+
+#### Info.plist file
+
+Please follow the instructions in [SwiftSample/InfoTemplate.plist](SwiftSample/InfoTemplate.plist) to generate the right Info.plist file
+
+#### Getting your own Credential files
+
+Please follow the instructions in [SwiftSample/AuthCredentialsTemplate.swift](SwiftSample/AuthCredentialsTemplate.swift) to generate the AuthCredentials.swift file.
+
+
+## Usage
+
+```
+$ pod update
+$ open Samples.xcworkspace
+```
+Then select a scheme and run.
diff --git a/AuthSamples/Sample/ApplicationDelegate.h b/AuthSamples/Sample/ApplicationDelegate.h
new file mode 100644
index 0000000..110348d
--- /dev/null
+++ b/AuthSamples/Sample/ApplicationDelegate.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol OpenURLDelegate <NSObject>
+
+/** @fn handleOpenURL:sourceApplication:
+ @brief Handles application:openURL:... methods for @c UIApplicationDelegate .
+ */
+- (BOOL)handleOpenURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication;
+
+@end
+
+/** @class ApplicationDelegate
+ @brief The sample application's delegate.
+ */
+@interface ApplicationDelegate : UIResponder <UIApplicationDelegate>
+
+/** @property window
+ @brief The sample application's @c UIWindow.
+ */
+@property(strong, nonatomic) UIWindow *window;
+
+/** @fn setOpenURLDelegate:
+ @brief Sets the delegate to handle application:openURL:... methods.
+ @param openURLDelegate The delegate which is not retained by this method.
+ */
++ (void)setOpenURLDelegate:(nullable id<OpenURLDelegate>)openURLDelegate;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/ApplicationDelegate.m b/AuthSamples/Sample/ApplicationDelegate.m
new file mode 100644
index 0000000..48577ff
--- /dev/null
+++ b/AuthSamples/Sample/ApplicationDelegate.m
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "ApplicationDelegate.h"
+
+#import "FIRApp.h"
+#import "FirebaseAuth.h"
+#import "AuthProviders.h"
+#import "MainViewController.h"
+
+#if INTERNAL_GOOGLE3_BUILD
+#import "googlemac/iPhone/Identity/Firebear/InternalUtils/FIRSessionFetcherLogging.h"
+#import "third_party/firebase/ios/Source/FirebaseCore/Library/Private/FIRLogger.h"
+#endif
+
+/** @var gOpenURLDelegate
+ @brief The delegate to for application:openURL:... method.
+ */
+static __weak id<OpenURLDelegate> gOpenURLDelegate;
+
+@implementation ApplicationDelegate
+
++ (void)setOpenURLDelegate:(nullable id<OpenURLDelegate>)openURLDelegate {
+ gOpenURLDelegate = openURLDelegate;
+}
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+#if INTERNAL_GOOGLE3_BUILD
+ [FIRSessionFetcherLogging setEnabled:YES];
+ FIRSetLoggerLevel(FIRLoggerLevelInfo);
+#endif
+
+ // Configure the default Firebase application:
+ [FIRApp configure];
+
+ // Load and present the UI:
+ UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ window.rootViewController =
+ [[MainViewController alloc] initWithNibName:NSStringFromClass([MainViewController class])
+ bundle:nil];
+ self.window = window;
+ [self.window makeKeyAndVisible];
+
+ return YES;
+}
+
+- (BOOL)application:(nonnull UIApplication *)application
+ openURL:(nonnull NSURL *)url
+ options:(nonnull NSDictionary<NSString *, id> *)options {
+ return [self application:application
+ openURL:url
+ sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
+ annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
+}
+
+- (BOOL)application:(UIApplication *)application
+ openURL:(NSURL *)url
+ sourceApplication:(NSString *)sourceApplication
+ annotation:(id)annotation {
+ if ([gOpenURLDelegate handleOpenURL:url sourceApplication:sourceApplication]) {
+ return YES;
+ }
+ return NO;
+}
+
+@end
diff --git a/AuthSamples/Sample/ApplicationTemplate.plist b/AuthSamples/Sample/ApplicationTemplate.plist
new file mode 100644
index 0000000..de0bba1
--- /dev/null
+++ b/AuthSamples/Sample/ApplicationTemplate.plist
@@ -0,0 +1,88 @@
+<!--
+ For this to be a valid plist file replace the following:
+ $REVERSE_CLIENT_ID:
+ Value of REVERSED_CLIENT_ID key in the GoogleService-Info.plist file.
+ $REVERSE_CLIENT_MULTI_ID:
+ Value of REVERSED_CLIENT_ID key in the GoogleService-Info_multi.plist file.
+ This step is optional. If you don't want to use advanced testing just remove
+ the entire dictionary.
+-->
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>Firebear SDK Sample</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>$REVERSE_CLIENT_ID</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>$REVERSE_CLIENT_ID</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>$REVERSE_CLIENT_MULTI_ID</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>$REVERSE_CLIENT_MULTI_ID</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIBackgroundModes</key>
+ <array>
+ <string>remote-notification</string>
+ </array>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>LSApplicationQueriesSchemes</key>
+ <array>
+ <string>fbauth2</string>
+ </array>
+</dict>
+</plist>
diff --git a/AuthSamples/Sample/AuthCredentialsTemplate.h b/AuthSamples/Sample/AuthCredentialsTemplate.h
new file mode 100644
index 0000000..17146d9
--- /dev/null
+++ b/AuthSamples/Sample/AuthCredentialsTemplate.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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.
+ */
+
+/*
+Some of the Auth Credentials needs to be populated for the Sample build to work.
+
+Please follow the following steps to populate the valid AuthCredentials
+and copy it to AuthCredentials.h file
+
+You will need to replace the following values:
+$KGOOGLE_CLIENT_ID
+Get the value of the CLIENT_ID key in the GoogleService-Info.plist file..
+
+$KFACEBOOK_APP_ID
+FACEBOOK_APP_ID is the developer's Facebook app's ID, to be used to test the
+'Signing in with Facebook' feature of Firebase Auth. Follow the instructions
+on the Facebook developer site: https://developers.facebook.com/docs/apps/register
+to obtain such an id
+
+$KAPP_ACCESS_TOKEN
+Once you have an Facebook App Id, click on dashboard from your app you can see
+both your App ID and the App Secret. Once you have both of these generate the
+access token using the step 13 of https://smashballoon.com/custom-facebook-feed/access-token/
+Follow the same link for comprehensive information on how to get the access token.
+
+$KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN
+GOOGLE_TEST_ACCOUNT_REFRESH_TOKEN is the Google SignIn refresh token obtained for the Google client ID,
+saved for continuous tests.
+
+The users that are behind these tokens must have user names as declared in the code, i.e.,
+"John Test" for Google and "MichaelTest" for Facebook, or the FirebearApiTests will fail.
+This can be found in ApiTests/FirebearApiTests.m with variable names kFacebookTestAccountName and
+kGoogleTestAccountName
+
+*/
+
+#define KAPP_ACCESS_TOKEN $KAPP_ACCESS_TOKEN
+#define KFACEBOOK_APP_ID $KFACEBOOK_APP_ID
+#define KGOOGLE_CLIENT_ID $KGOOGLE_CLIENT_ID
+#define KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN $KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN
diff --git a/AuthSamples/Sample/AuthProviders.h b/AuthSamples/Sample/AuthProviders.h
new file mode 100644
index 0000000..eccbad9
--- /dev/null
+++ b/AuthSamples/Sample/AuthProviders.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+@class FIRAuthCredential;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @typedef AuthCredentialCallback
+ @brief The type of block invoked when a @c FIRAuthCredential object is ready or an error has
+ occurred.
+ @param credential The auth credential if any.
+ @param error The error which occurred, if any.
+ */
+typedef void (^AuthCredentialCallback)(FIRAuthCredential *_Nullable credential,
+ NSError *_Nullable error);
+/** @protocol AuthProvider
+ @brief A common interface for auth providers to be used by the sample app.
+ */
+@protocol AuthProvider <NSObject>
+
+/** @fn getAuthCredentialWithPresentingViewController:callback:
+ @brief Gets a @c FIRAuthCredential instance for use with Firebase headless API by signing in.
+ @param viewController The view controller to present the UI.
+ @param callback A block which is invoked when the sign-in flow finishes. Invoked asynchronously
+ on an unspecified thread in the future.
+ */
+- (void)getAuthCredentialWithPresentingViewController:(UIViewController *)viewController
+ callback:(AuthCredentialCallback)callback;
+
+/** @fn signOut
+ @brief Logs out the current provider session, which invalidates any cached crendential.
+ */
+- (void)signOut;
+
+@end
+
+/** @class AuthProviders
+ @brief Namespace for @c AuthProvider instances.
+ */
+@interface AuthProviders : NSObject
+
+/** @fn google
+ @brief Returns a Google auth provider.
+ */
++ (id<AuthProvider>)google;
+
+/** @fn facebook
+ @brief Returns a Facebook auth provider.
+ */
++ (id<AuthProvider>)facebook;
+
+/** @fn init
+ @brief This class is not supposed to be instantiated.
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/AuthProviders.m b/AuthSamples/Sample/AuthProviders.m
new file mode 100644
index 0000000..825935f
--- /dev/null
+++ b/AuthSamples/Sample/AuthProviders.m
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "AuthProviders.h"
+
+#import "FacebookAuthProvider.h"
+#import "GoogleAuthProvider.h"
+
+@implementation AuthProviders
+
++ (id<AuthProvider>)google {
+ static id<AuthProvider> googleAuthProvider;
+ if (!googleAuthProvider) {
+ googleAuthProvider = [[GoogleAuthProvider alloc] init];
+ }
+ return googleAuthProvider;
+}
+
++ (id<AuthProvider>)facebook {
+ static id<AuthProvider> facebookAuthProvider;
+ if (!facebookAuthProvider) {
+ facebookAuthProvider = [[FacebookAuthProvider alloc] init];
+ }
+ return facebookAuthProvider;
+}
+
+@end
diff --git a/AuthSamples/Sample/CustomTokenDataEntryViewController.h b/AuthSamples/Sample/CustomTokenDataEntryViewController.h
new file mode 100644
index 0000000..e783bc7
--- /dev/null
+++ b/AuthSamples/Sample/CustomTokenDataEntryViewController.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @typedef CustomTokenDataEntryViewControllerCompletion
+ @brief The type of callback block invoked when a @c CustomTokenDataEntryViewController is
+ dismissed (by either being cancelled or completed by the user.)
+ @param cancelled Indicates the user cancelled the flow and didn't want to enter a token.
+ @param userEnteredTokenText The token text the user entered.
+ */
+typedef void (^CustomTokenDataEntryViewControllerCompletion)
+ (BOOL cancelled, NSString *_Nullable userEnteredTokenText);
+
+/** @class CustomTokenDataEntryViewController
+ @brief Simple view controller to allow data entry of custom BYOAuth tokens.
+ */
+@interface CustomTokenDataEntryViewController : UIViewController
+
+/** @fn initWithNibName:bundle:
+ @brief Please use initWithCompletion:
+ */
+- (instancetype)initWithNibName:(NSString *_Nullable)nibNameOrNil
+ bundle:(NSBundle *_Nullable)nibBundleOrNil NS_UNAVAILABLE;
+
+/** @fn initWithCoder:
+ @brief Please use initWithCompletion:
+ */
+- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
+
+/** @fn initWithCompletion:
+ @brief Designated initializer.
+ @param completion A block which will be invoked when the user either chooses "cancel" or "done".
+ */
+- (nullable instancetype)initWithCompletion:
+ (CustomTokenDataEntryViewControllerCompletion)completion NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/CustomTokenDataEntryViewController.m b/AuthSamples/Sample/CustomTokenDataEntryViewController.m
new file mode 100644
index 0000000..b65c244
--- /dev/null
+++ b/AuthSamples/Sample/CustomTokenDataEntryViewController.m
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "CustomTokenDataEntryViewController.h"
+
+/** @var navigationBarDefaultHeight
+ @brief The default height to use for new navigation bars' frames.
+ */
+static const NSUInteger navigationBarDefaultHeight = 55;
+
+/** @var navigationBarSystemHeight
+ @brief Set after a navigation bar has been created to obtain the system-specified height of the
+ navigation bar.
+ */
+static NSUInteger navigationBarSystemHeight = navigationBarDefaultHeight;
+
+/** @var kTitle
+ @brief The title of the view controller as it appears at the top of the screen in the navigation
+ bar.
+ */
+static NSString *const kTitle = @"Enter Token";
+
+/** @var kCancel
+ @brief The text for the "Cancel" button.
+ */
+static NSString *const kCancel = @"Cancel";
+
+/** @var kDone
+ @brief The text for the "Done" button.
+ */
+static NSString *const kDone = @"Done";
+
+@implementation CustomTokenDataEntryViewController {
+ /** @var _completion
+ @brief The block we will call when the user presses the "cancel" or "done" buttons.
+ @remarks Passed into the initializer.
+ */
+ CustomTokenDataEntryViewControllerCompletion _completion;
+
+ /** @var _tokenTextView
+ @brief The text view allowing the user to enter their custom token text.
+ @remarks Constructed and set in the method: @c loadTextView.
+ */
+ __weak UITextView *_Nullable _tokenTextView;
+}
+
+- (nullable instancetype)initWithCompletion:
+ (CustomTokenDataEntryViewControllerCompletion)completion {
+ self = [super initWithNibName:nil bundle:nil];
+ if (self) {
+ _completion = completion;
+ }
+ return self;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self loadHeader];
+ [self loadTextView];
+}
+
+#pragma mark - View
+
+/** @fn loadHeader
+ @brief Loads the header bar along the top of the view with "Cancel" and "Done" buttons, as well
+ as a brief title asking the user to enter the custom token text.
+ @remarks Updates navigationBarSystemHeight, so should be called before any method which depends
+ on that variable being updated (like the @c loadTextView method, which uses the value to
+ determine how much room is left on the screen.)
+ */
+- (void)loadHeader {
+ CGRect navBarFrame = CGRectMake(0, 0, self.view.bounds.size.width, navigationBarDefaultHeight);
+ UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:navBarFrame];
+ navBar.autoresizingMask = UIViewAutoresizingFlexibleWidth
+ | UIViewAutoresizingFlexibleBottomMargin;
+
+ UINavigationItem *navItem = [[UINavigationItem alloc] initWithTitle:kTitle];
+ UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:kCancel
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(cancelPressed:)];
+ UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:kDone
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(donePressed:)];
+ navItem.leftBarButtonItem = cancelButton;
+ navItem.rightBarButtonItem = doneButton;
+
+ [navBar setItems:@[ navItem ] animated:NO];
+
+ [self.view addSubview:navBar];
+
+ // Obtain the system-specified height of the navigation bar.
+ navigationBarSystemHeight = navBar.frame.size.height;
+}
+
+/** @fn loadTextView
+ @brief Loads the text field for the user to enter their custom token text.
+ @remarks Relies on the navigationBarSystemHeight variable being correct.
+ */
+- (void)loadTextView {
+ CGRect tokenTextViewFrame =
+ CGRectMake(0,
+ navigationBarSystemHeight,
+ self.view.bounds.size.width,
+ self.view.bounds.size.height - navigationBarSystemHeight);
+ UITextView *tokenTextView = [[UITextView alloc] initWithFrame:tokenTextViewFrame];
+ tokenTextView.backgroundColor = [UIColor whiteColor];
+ tokenTextView.textAlignment = NSTextAlignmentLeft;
+
+ [self.view addSubview:tokenTextView];
+ _tokenTextView = tokenTextView;
+}
+
+#pragma mark - Actions
+
+- (void)cancelPressed:(id)sender {
+ [self finishByCancelling:YES withUserEnteredTokenText:nil];
+}
+
+- (void)donePressed:(id)sender {
+ [self finishByCancelling:NO withUserEnteredTokenText:_tokenTextView.text];
+}
+
+#pragma mark - Workflow
+
+- (void)finishByCancelling:(BOOL)cancelled
+ withUserEnteredTokenText:(nullable NSString *)userEnteredTokenText {
+ [self dismissViewControllerAnimated:YES completion:^{
+ _completion(cancelled,
+ cancelled ? nil : userEnteredTokenText);
+ }];
+}
+
+@end
diff --git a/AuthSamples/Sample/FacebookAuthProvider.h b/AuthSamples/Sample/FacebookAuthProvider.h
new file mode 100644
index 0000000..6c2edaf
--- /dev/null
+++ b/AuthSamples/Sample/FacebookAuthProvider.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+#import "AuthProviders.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FacebookAuthProvider
+ @brief The implementation for Facebook auth provider related methods.
+ */
+@interface FacebookAuthProvider : NSObject <AuthProvider>
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/FacebookAuthProvider.m b/AuthSamples/Sample/FacebookAuthProvider.m
new file mode 100644
index 0000000..19ac4c8
--- /dev/null
+++ b/AuthSamples/Sample/FacebookAuthProvider.m
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "FacebookAuthProvider.h"
+
+#import "FIRFacebookAuthProvider.h"
+#import "ApplicationDelegate.h"
+#import "AuthCredentials.h"
+#import "FBSDKCoreKit.h"
+#import "FBSDKLoginKit.h"
+
+/** @var kFacebookAppId
+ @brief The App ID for the Facebook SDK.
+ */
+static NSString *const kFacebookAppID = KFACEBOOK_APP_ID;
+
+@interface FacebookAuthProvider () <OpenURLDelegate>
+@end
+
+@implementation FacebookAuthProvider {
+ FBSDKLoginManager *_loginManager;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _loginManager = [[FBSDKLoginManager alloc] init];
+ }
+ return self;
+}
+
+- (void)getAuthCredentialWithPresentingViewController:(UIViewController *)viewController
+ callback:(AuthCredentialCallback)callback {
+ [self signOut];
+
+ [ApplicationDelegate setOpenURLDelegate:self];
+ [FBSDKSettings setAppID:kFacebookAppID];
+ [_loginManager logInWithReadPermissions:@[ @"email" ]
+ fromViewController:viewController
+ handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
+ [ApplicationDelegate setOpenURLDelegate:nil];
+ if (!error && result.isCancelled) {
+ error = [NSError errorWithDomain:@"com.google.FirebearSample" code:-1 userInfo:nil];
+ }
+ if (error) {
+ callback(nil, error);
+ return;
+ }
+ NSString *accessToken = [FBSDKAccessToken currentAccessToken].tokenString;
+ callback([FIRFacebookAuthProvider credentialWithAccessToken:accessToken], nil);
+ }];
+}
+
+- (void)signOut {
+ [_loginManager logOut];
+}
+
+- (BOOL)handleOpenURL:(NSURL *)URL sourceApplication:(NSString *)sourceApplication {
+ return [[FBSDKApplicationDelegate sharedInstance] application:[UIApplication sharedApplication]
+ openURL:URL
+ sourceApplication:sourceApplication
+ annotation:nil];
+}
+
+@end
diff --git a/AuthSamples/Sample/GoogleAuthProvider.h b/AuthSamples/Sample/GoogleAuthProvider.h
new file mode 100644
index 0000000..50679a6
--- /dev/null
+++ b/AuthSamples/Sample/GoogleAuthProvider.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+#import "AuthProviders.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class GoogleAuthProvider
+ @brief The implementation for Google auth provider related methods.
+ */
+@interface GoogleAuthProvider : NSObject <AuthProvider>
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/GoogleAuthProvider.m b/AuthSamples/Sample/GoogleAuthProvider.m
new file mode 100644
index 0000000..a1db437
--- /dev/null
+++ b/AuthSamples/Sample/GoogleAuthProvider.m
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "GoogleAuthProvider.h"
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+#import "FIRGoogleAuthProvider.h"
+#import "ApplicationDelegate.h"
+
+@import GoogleSignIn;
+
+
+/** @typedef GoogleSignInCallback
+ @brief The type of block invoked when a @c GIDGoogleUser object is ready or an error has
+ occurred.
+ @param user The Google user if any.
+ @param error The error which occurred, if any.
+ */
+typedef void (^GoogleSignInCallback)(GIDGoogleUser *user, NSError *error);
+
+/** @class GoogleAuthDelegate
+ @brief The designated delegate class for Google Sign-In.
+ @param callback A block which is invoked when the sign-in flow finishes. Invoked asynchronously
+ on an unspecified thread in the future.
+ */
+@interface GoogleAuthDelegate : NSObject <GIDSignInDelegate, GIDSignInUIDelegate, OpenURLDelegate>
+
+/** @fn initWithPresentingViewController:callback:
+ @brief Initializes the new instance with the callback.
+ @param presentingViewController The view controller to present the UI.
+ @param callback A block which is invoked when the sign-in flow finishes. Invoked asynchronously
+ on an unspecified thread in the future.
+ */
+- (instancetype)initWithPresentingViewController:(UIViewController *)presentingViewController
+ callback:(nullable GoogleSignInCallback)callback;
+
+@end
+
+@implementation GoogleAuthDelegate {
+ UIViewController *_presentingViewController;
+ GoogleSignInCallback _callback;
+}
+
+- (instancetype)initWithPresentingViewController:(UIViewController *)presentingViewController
+ callback:(nullable GoogleSignInCallback)callback {
+ self = [super init];
+ if (self) {
+ _presentingViewController = presentingViewController;
+ _callback = callback;
+ }
+ return self;
+}
+
+- (void)signIn:(GIDSignIn *)signIn
+ didSignInForUser:(GIDGoogleUser *)user
+ withError:(NSError *)error {
+ GoogleSignInCallback callback = _callback;
+ _callback = nil;
+ if (callback) {
+ callback(user, error);
+ }
+}
+
+- (void)signIn:(GIDSignIn *)signIn presentViewController:(UIViewController *)viewController {
+ [_presentingViewController presentViewController:viewController animated:YES completion:nil];
+}
+
+- (void)signIn:(GIDSignIn *)signIn dismissViewController:(UIViewController *)viewController {
+ [_presentingViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (BOOL)handleOpenURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
+ return [[GIDSignIn sharedInstance] handleURL:url
+ sourceApplication:sourceApplication
+ annotation:nil];
+}
+
+@end
+
+@implementation GoogleAuthProvider
+
+- (void)getAuthCredentialWithPresentingViewController:(UIViewController *)viewController
+ callback:(AuthCredentialCallback)callback {
+ [self signOut];
+
+ // The delegate needs to be retained.
+ __block GoogleAuthDelegate *delegate = [[GoogleAuthDelegate alloc]
+ initWithPresentingViewController:viewController
+ callback:^(GIDGoogleUser *user, NSError *error) {
+ [ApplicationDelegate setOpenURLDelegate:nil];
+ delegate = nil;
+ if (error) {
+ callback(nil, error);
+ return;
+ }
+ GIDAuthentication *auth = user.authentication;
+ FIRAuthCredential *credential = [FIRGoogleAuthProvider credentialWithIDToken:auth.idToken
+ accessToken:auth.accessToken];
+ callback(credential, error);
+ }];
+ GIDSignIn *signIn = [GIDSignIn sharedInstance];
+ signIn.clientID = [self googleClientID];
+ signIn.shouldFetchBasicProfile = YES;
+ signIn.delegate = delegate;
+ signIn.uiDelegate = delegate;
+ [ApplicationDelegate setOpenURLDelegate:delegate];
+ [signIn signIn];
+}
+
+- (void)signOut {
+ [[GIDSignIn sharedInstance] signOut];
+}
+
+- (NSString *)googleClientID {
+ return [FIRApp defaultApp].options.clientID;
+}
+
+@end
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/Contents.json b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..4f6afa6
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,177 @@
+{
+ "images" : [
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_21in29dp-1.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_21in29dp-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_3x_ios_21in29dp.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_29in40dp-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_3x_ios_29in40dp-1.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_42in57dp.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_42in57dp.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_44in60dp.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "logo_avatar_square_grey_color_3x_ios_44in60dp.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_21in29dp.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_21in29dp.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_29in40dp-1.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_29in40dp-2.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_38in50dp.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_38in50dp.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_53in72dp.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_53in72dp.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_1x_ios_56in76dp.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "logo_avatar_square_grey_color_2x_ios_56in76dp.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "16x16",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "32x32",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "128x128",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "256x256",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "512x512",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "mac",
+ "size" : "512x512",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp-1.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp-1.png
new file mode 100644
index 0000000..2976035
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp-1.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp.png
new file mode 100644
index 0000000..2976035
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_29in40dp-1.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_29in40dp-1.png
new file mode 100644
index 0000000..32684ce
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_29in40dp-1.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_38in50dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_38in50dp.png
new file mode 100644
index 0000000..0c98554
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_38in50dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_42in57dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_42in57dp.png
new file mode 100644
index 0000000..3ef403a
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_42in57dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_53in72dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_53in72dp.png
new file mode 100644
index 0000000..ff6c804
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_53in72dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_56in76dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_56in76dp.png
new file mode 100644
index 0000000..df8a953
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_56in76dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp-1.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp-1.png
new file mode 100644
index 0000000..4067017
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp-1.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp.png
new file mode 100644
index 0000000..4067017
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-1.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-1.png
new file mode 100644
index 0000000..9452a26
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-1.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-2.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-2.png
new file mode 100644
index 0000000..9452a26
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_29in40dp-2.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_38in50dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_38in50dp.png
new file mode 100644
index 0000000..436be10
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_38in50dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_42in57dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_42in57dp.png
new file mode 100644
index 0000000..e9c869e
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_42in57dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_44in60dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_44in60dp.png
new file mode 100644
index 0000000..8c5ce9d
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_44in60dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_53in72dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_53in72dp.png
new file mode 100644
index 0000000..0ddd720
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_53in72dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_56in76dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_56in76dp.png
new file mode 100644
index 0000000..2f028cb
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_56in76dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_21in29dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_21in29dp.png
new file mode 100644
index 0000000..69bb8d3
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_21in29dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_29in40dp-1.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_29in40dp-1.png
new file mode 100644
index 0000000..7045675
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_29in40dp-1.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_44in60dp.png b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_44in60dp.png
new file mode 100644
index 0000000..211ef93
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_44in60dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/Contents.json b/AuthSamples/Sample/Images.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/AuthSamples/Sample/Images.xcassets/close.imageset/Contents.json b/AuthSamples/Sample/Images.xcassets/close.imageset/Contents.json
new file mode 100644
index 0000000..b38b207
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/close.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "ic_clear_black_1x_ios_24dp.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "ic_clear_black_2x_ios_24dp.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "ic_clear_black_3x_ios_24dp.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_1x_ios_24dp.png b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_1x_ios_24dp.png
new file mode 100644
index 0000000..40a1a84
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_1x_ios_24dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_2x_ios_24dp.png b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_2x_ios_24dp.png
new file mode 100644
index 0000000..6bc4372
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_2x_ios_24dp.png
Binary files differ
diff --git a/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_3x_ios_24dp.png b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_3x_ios_24dp.png
new file mode 100644
index 0000000..51b4401
--- /dev/null
+++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_3x_ios_24dp.png
Binary files differ
diff --git a/AuthSamples/Sample/MainViewController.h b/AuthSamples/Sample/MainViewController.h
new file mode 100644
index 0000000..d2390b1
--- /dev/null
+++ b/AuthSamples/Sample/MainViewController.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+@class StaticContentTableViewManager;
+@class UserTableViewCell;
+
+/** @var kCreateUserAccessibilityID
+ @brief The "Create User" button accessibility ID.
+ */
+extern NSString *const kCreateUserAccessibilityID;
+
+/** @class MainViewController
+ @brief The first view controller presented when the application is started.
+ */
+@interface MainViewController : UIViewController
+
+/** @property tableViewManager
+ @brief A @c StaticContentTableViewManager which is used to manage the contents of the table
+ view.
+ */
+@property(nonatomic, strong) IBOutlet StaticContentTableViewManager *tableViewManager;
+
+/** @property tableView
+ @brief A UITableView which is used to display user info and a list of actions.
+ */
+@property(nonatomic, weak) IBOutlet UITableView *tableView;
+
+/** @property userInfoTableViewCell
+ @brief A custom UITableViewCell for displaying the user info.
+ */
+@property(nonatomic, strong) IBOutlet UserTableViewCell *userInfoTableViewCell;
+
+/** @property userInMemoryInfoTableViewCell
+ @brief A custom UITableViewCell for displaying the user info.
+ */
+@property(nonatomic, strong) IBOutlet UserTableViewCell *userInMemoryInfoTableViewCell;
+
+/** @property userToUseCell
+ @brief A custom UITableViewCell for choosing which user to use for user operations (either the
+ currently signed-in user, or the user in "memory".
+ */
+@property(nonatomic, strong) IBOutlet UITableViewCell *userToUseCell;
+
+/** @property consoleTextView
+ @brief A UITextView with a log of the actions performed in the sample app.
+ */
+@property(nonatomic, weak) IBOutlet UITextView *consoleTextView;
+
+/** @fn userToUseDidChange:
+ @brief Should be invoked when the user wishes to switch which user to use for user-related
+ operations in the sample app.
+ @param sender The UISegmentedControl which prompted the change in value. It is assumed that the
+ segment at index 0 represents the "signed-in user" and the segment at index 1 represents the
+ "user in memeory".
+ */
+- (IBAction)userToUseDidChange:(UISegmentedControl *)sender;
+
+/** @fn memoryPlus
+ @brief Works like the "M+" button on a calculator; stores the currently signed-in user as the
+ "user in memory" for the application.
+ */
+- (IBAction)memoryPlus;
+
+/** @fn memoryClear
+ @brief Works like the "MC" button on a calculator; clears the currently stored "user in memory"
+ for the application.
+ */
+- (IBAction)memoryClear;
+
+@end
diff --git a/AuthSamples/Sample/MainViewController.m b/AuthSamples/Sample/MainViewController.m
new file mode 100644
index 0000000..3013a3b
--- /dev/null
+++ b/AuthSamples/Sample/MainViewController.m
@@ -0,0 +1,2496 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "MainViewController.h"
+
+#import <objc/runtime.h>
+
+#import "FIRAdditionalUserInfo.h"
+#import "FIRApp.h"
+#import "FIROAuthProvider.h"
+#import "FIRPhoneAuthCredential.h"
+#import "FIRPhoneAuthProvider.h"
+#import "FirebaseAuth.h"
+#import "CustomTokenDataEntryViewController.h"
+#import "FacebookAuthProvider.h"
+#import "GoogleAuthProvider.h"
+#import "SettingsViewController.h"
+#import "StaticContentTableViewManager.h"
+#import "UIViewController+Alerts.h"
+#import "UserInfoViewController.h"
+#import "UserTableViewCell.h"
+
+#if INTERNAL_GOOGLE3_BUILD
+#import "third_party/objective_c/FirebaseDatabase/FirebaseDatabase.framework/Headers/FirebaseDatabase.h"
+#endif
+
+/** @var kTokenGetButtonText
+ @brief The text of the "Get Token" button.
+ */
+static NSString *const kTokenGetButtonText = @"Get Token";
+
+/** @var kTokenRefreshButtonText
+ @brief The text of the "Refresh Token" button.
+ */
+static NSString *const kTokenRefreshButtonText = @"Force Refresh Token";
+
+/** @var kTokenRefreshedAlertTitle
+ @brief The title of the "Token Refreshed" alert.
+ */
+static NSString *const kTokenRefreshedAlertTitle = @"Token";
+
+/** @var kTokenRefreshErrorAlertTitle
+ @brief The title of the "Token Refresh error" alert.
+ */
+static NSString *const kTokenRefreshErrorAlertTitle = @"Get Token Error";
+
+/** @var kSettingsButtonText
+ @brief The text of the "Settings" button.
+ */
+static NSString *const kSettingsButtonText = @"[Sample App Settings]";
+
+/** @var kUserInfoButtonText
+ @brief The text of the "Show User Info" button.
+ */
+static NSString *const kUserInfoButtonText = @"[Show User Info]";
+
+/** @var kSetImageURLText
+ @brief The text of the "Set Photo url" button.
+ */
+static NSString *const kSetPhotoURLText = @"Set Photo url";
+
+/** @var kSignInButtonText
+ @brief The text of the "Sign In" button.
+ */
+static NSString *const kSignInButtonText = @"Sign In (HEADFUL)";
+
+/** @var kSignInGoogleButtonText
+ @brief The text of the "Google SignIn" button.
+ */
+static NSString *const kSignInGoogleButtonText = @"Sign in with Google";
+
+/** @var kSignInAndRetrieveGoogleButtonText
+ @brief The text of the "Sign in with Google and retrieve data" button.
+ */
+static NSString *const kSignInGoogleAndRetrieveDataButtonText =
+ @"Sign in with Google and retrieve data";
+
+/** @var kSignInFacebookButtonText
+ @brief The text of the "Facebook SignIn" button.
+ */
+static NSString *const kSignInFacebookButtonText = @"Sign in with Facebook";
+
+/** @var kSignInFacebookAndRetrieveDataButtonText
+ @brief The text of the "Facebook SignIn and retrieve data" button.
+ */
+static NSString *const kSignInFacebookAndRetrieveDataButtonText =
+ @"Sign in with Facebook and retrieve data";
+
+/** @var kSignInEmailPasswordButtonText
+ @brief The text of the "Email/Password SignIn" button.
+ */
+static NSString *const kSignInEmailPasswordButtonText = @"Sign in with Email/Password";
+
+/** @var kSignInWithCustomTokenButtonText
+ @brief The text of the "Sign In (BYOAuth)" button.
+ */
+static NSString *const kSignInWithCustomTokenButtonText = @"Sign In (BYOAuth)";
+
+/** @var kSignInAnonymouslyButtonText
+ @brief The text of the "Sign In Anonymously" button.
+ */
+static NSString *const kSignInAnonymouslyButtonText = @"Sign In Anonymously";
+
+/** @var kSignedInAlertTitle
+ @brief The text of the "Sign In Succeeded" alert.
+ */
+static NSString *const kSignedInAlertTitle = @"Signed In";
+
+/** @var kSignInErrorAlertTitle
+ @brief The text of the "Sign In Encountered an Error" alert.
+ */
+static NSString *const kSignInErrorAlertTitle = @"Sign-In Error";
+
+/** @var kSignOutButtonText
+ @brief The text of the "Sign Out" button.
+ */
+static NSString *const kSignOutButtonText = @"Sign Out";
+
+/** @var kDeleteAccountText
+ @brief The text of the "Delete Account" button.
+ */
+static NSString *const kDeleteUserText = @"Delete Account";
+
+/** @var kReauthenticateGoogleText
+ @brief The text of the "Reathenticate Google" button.
+ */
+static NSString *const kReauthenticateGoogleText = @"Reauthenticate Google";
+
+/** @var kReauthenticateGoogleAndRetrieveDataText
+ @brief The text of the "Reathenticate Google and retrieve data" button.
+ */
+static NSString *const kReauthenticateGoogleAndRetrieveDataText =
+ @"Reauthenticate Google and retrieve data";
+
+/** @var kReauthenticateFBText
+ @brief The text of the "Reathenticate Facebook" button.
+ */
+static NSString *const kReauthenticateFBText = @"Reauthenticate FB";
+
+/** @var kReauthenticateFBAndRetrieveDataText
+ @brief The text of the "Reathenticate Facebook and retrieve data" button.
+ */
+static NSString *const kReauthenticateFBAndRetrieveDataText =
+ @"Reauthenticate FB and retrieve data";
+
+/** @var kReAuthenticateEmail
+ @brief The text of the "Reathenticate Email" button.
+ */
+static NSString *const kReauthenticateEmailText = @"Reauthenticate Email/Password";
+
+/** @var kOKButtonText
+ @brief The text of the "OK" button for the Sign In result dialogs.
+ */
+static NSString *const kOKButtonText = @"OK";
+
+/** @var kSetDisplayNameTitle
+ @brief The title of the "Set Display Name" error dialog.
+ */
+static NSString *const kSetDisplayNameTitle = @"Set Display Name";
+
+/** @var kUpdateEmailText
+ @brief The title of the "Update Email" button.
+ */
+static NSString *const kUpdateEmailText = @"Update Email";
+
+/** @var kUpdatePasswordText
+ @brief The title of the "Update Password" button.
+ */
+static NSString *const kUpdatePasswordText = @"Update Password";
+
+/** @var kUpdatePhoneNumber
+ @brief The title of the "Update Photo" button.
+ */
+static NSString *const kUpdatePhoneNumber = @"Update Phone Number";
+
+/** @var kLinkPhoneNumber
+ @brief The title of the "Link phone" button.
+ */
+static NSString *const kLinkPhoneNumber = @"Link Phone Number";
+
+/** @var kUnlinkPhone
+ @brief The title of the "Unlink Phone" button for unlinking phone auth provider.
+ */
+static NSString *const kUnlinkPhoneNumber = @"Unlink Phone Number";
+
+/** @var kReloadText
+ @brief The title of the "Reload User" button.
+ */
+static NSString *const kReloadText = @"Reload User";
+
+/** @var kLinkWithGoogleText
+ @brief The title of the "Link with Google" button.
+ */
+static NSString *const kLinkWithGoogleText = @"Link with Google";
+
+/** @var kLinkWithGoogleAndRetrieveDataText
+ @brief The title of the "Link with Google and retrieve data" button.
+ */
+static NSString *const kLinkWithGoogleAndRetrieveDataText = @"Link with Google and retrieve data";
+
+/** @var kLinkWithFacebookText
+ @brief The title of the "Link with Facebook Account" button.
+ */
+static NSString *const kLinkWithFacebookText = @"Link with Facebook";
+
+/** @var kLinkWithFacebookAndRetrieveDataText
+ @brief The title of the "Link with Facebook and retrieve data" button.
+ */
+static NSString *const kLinkWithFacebookAndRetrieveDataText =
+ @"Link with Facebook and retrieve data";
+
+/** @var kLinkWithEmailPasswordText
+ @brief The title of the "Link with Email/Password Account" button.
+ */
+static NSString *const kLinkWithEmailPasswordText = @"Link with Email/Password";
+
+/** @var kUnlinkTitle
+ @brief The text of the "Unlink from Provider" error Dialog.
+ */
+static NSString *const kUnlinkTitle = @"Unlink from Provider";
+
+/** @var kUnlinkFromGoogle
+ @brief The text of the "Unlink from Google" button.
+ */
+static NSString *const kUnlinkFromGoogle = @"Unlink from Google";
+
+/** @var kUnlinkFromFacebook
+ @brief The text of the "Unlink from Facebook" button.
+ */
+static NSString *const kUnlinkFromFacebook = @"Unlink from Facebook";
+
+/** @var kUnlinkFromEmailPassword
+ @brief The text of the "Unlink from Google" button.
+ */
+static NSString *const kUnlinkFromEmailPassword = @"Unlink from Email/Password";
+
+/** @var kGetProvidersForEmail
+ @brief The text of the "Get Provider IDs for Email" button.
+ */
+static NSString *const kGetProvidersForEmail = @"Get Provider IDs for Email";
+
+/** @var kRequestVerifyEmail
+ @brief The text of the "Request Verify Email Link" button.
+ */
+static NSString *const kRequestVerifyEmail = @"Request Verify Email Link";
+
+/** @var kRequestPasswordReset
+ @brief The text of the "Email Password Reset" button.
+ */
+static NSString *const kRequestPasswordReset = @"Send Password Reset Email";
+
+/** @var kResetPassword
+ @brief The text of the "Password Reset" button.
+ */
+static NSString *const kResetPassword = @"Reset Password";
+
+/** @var kCheckActionCode
+ @brief The text of the "Check action code" button.
+ */
+static NSString *const kCheckActionCode = @"Check action code";
+
+/** @var kApplyActionCode
+ @brief The text of the "Apply action code" button.
+ */
+static NSString *const kApplyActionCode = @"Apply action code";
+
+/** @var kVerifyPasswordResetCode
+ @brief The text of the "Verify password reset code" button.
+ */
+static NSString *const kVerifyPasswordResetCode = @"Verify password reset code";
+
+/** @var kSectionTitleSettings
+ @brief The text for the title of the "Settings" section.
+ */
+static NSString *const kSectionTitleSettings = @"SETTINGS";
+
+/** @var kSectionTitleSignIn
+ @brief The text for the title of the "Sign-In" section.
+ */
+static NSString *const kSectionTitleSignIn = @"SIGN-IN";
+
+/** @var kSectionTitleReauthenticate
+ @brief The text for the title of the "Reauthenticate" section.
+ */
+static NSString *const kSectionTitleReauthenticate = @"REAUTHENTICATE";
+
+/** @var kSectionTitleTokenActions
+ @brief The text for the title of the "Token Actions" section.
+ */
+static NSString *const kSectionTitleTokenActions = @"TOKEN ACTIONS";
+
+/** @var kSectionTitleEditUser
+ @brief The text for the title of the "Edit User" section.
+ */
+static NSString *const kSectionTitleEditUser = @"EDIT USER";
+
+/** @var kSectionTitleLinkUnlinkAccount
+ @brief The text for the title of the "Link/Unlink account" section.
+ */
+static NSString *const kSectionTitleLinkUnlinkAccounts = @"LINK/UNLINK ACCOUNT";
+
+/** @var kSectionTitleUserActions
+ @brief The text for the title of the "User Actions" section.
+ */
+static NSString *const kSectionTitleUserActions = @"USER ACTIONS";
+
+/** @var kSectionTitleUserDetails
+ @brief The text for the title of the "User Details" section.
+ */
+static NSString *const kSectionTitleUserDetails = @"SIGNED-IN USER DETAILS";
+
+/** @var kSectionTitleListeners
+ @brief The title for the table view section dedicated to auth state did change listeners.
+ */
+static NSString *const kSectionTitleListeners = @"Listeners";
+
+/** @var kAddAuthStateListenerTitle
+ @brief The title for the table view row which adds a block to the auth state did change
+ listeners.
+ */
+static NSString *const kAddAuthStateListenerTitle = @"Add Auth State Change Listener";
+
+/** @var kRemoveAuthStateListenerTitle
+ @brief The title for the table view row which removes a block to the auth state did change
+ listeners.
+ */
+static NSString *const kRemoveAuthStateListenerTitle = @"Remove Last Auth State Change Listener";
+
+/** @var kAddIDTokenListenerTitle
+ @brief The title for the table view row which adds a block to the ID token did change
+ listeners.
+ */
+static NSString *const kAddIDTokenListenerTitle = @"Add ID Token Change Listener";
+
+/** @var kRemoveIDTokenListenerTitle
+ @brief The title for the table view row which removes a block to the ID token did change
+ listeners.
+ */
+static NSString *const kRemoveIDTokenListenerTitle = @"Remove Last ID Token Change Listener";
+
+/** @var kSectionTitleApp
+ @brief The text for the title of the "App" section.
+ */
+static NSString *const kSectionTitleApp = @"APP";
+
+/** @var kCreateUserTitle
+ @brief The text of the "Create User" button.
+ */
+static NSString *const kCreateUserTitle = @"Create User";
+
+/** @var kDeleteAppTitle
+ @brief The text of the "Delete App" button.
+ */
+static NSString *const kDeleteAppTitle = @"Delete App";
+
+/** @var kSectionTitleManualTests
+ @brief The section title for automated manual tests.
+ */
+static NSString *const kSectionTitleManualTests = @"Manual Tests";
+
+/** @var kAutoBYOAuthTitle
+ @brief The button title for automated BYOAuth operation.
+ */
+static NSString *const kAutoBYOAuthTitle = @"BYOAuth";
+
+/** @var kAutoSignInGoogle
+ @brief The button title for automated Google sign-in operation.
+ */
+static NSString *const kAutoSignInGoogle = @"Sign In With Google";
+
+/** @var kAutoSignInFacebook
+ @brief The button title for automated Facebook sign-in operation.
+ */
+static NSString *const kAutoSignInFacebook = @"Sign In With Facebook";
+
+/** @var kAutoSignUpEmailPassword
+ @brief The button title for automated sign-up with email/password.
+ */
+static NSString *const kAutoSignUpEmailPassword = @"Sign Up With Email/Password";
+
+/** @var kAutoSignInAnonymously
+ @brief The button title for automated sign-in anonymously.
+ */
+static NSString *const kAutoSignInAnonymously = @"Sign In Anonymously";
+
+/** @var kAutoAccountLinking
+ @brief The button title for automated account linking.
+ */
+static NSString *const kAutoAccountLinking = @"Link with Google";
+
+/** @var kGitHubSignInButtonText
+ @brief The button title for signing in with github.
+ */
+static NSString *const kGitHubSignInButtonText = @"Sign In with GitHub";
+
+/** @var kExpiredCustomTokenUrl
+ @brief The url for obtaining a valid custom token string used to test BYOAuth.
+ */
+static NSString *const kCustomTokenUrl = @"https://fb-sa-1211.appspot.com/token";
+
+/** @var kCustomTokenUrl
+ @brief The url for obtaining an expired custom token string used to test BYOAuth.
+ */
+static NSString *const kExpiredCustomTokenUrl = @"https://fb-sa-1211.appspot.com/expired_token";
+
+/** @var kFakeDisplayPhotoUrl
+ @brief The url for obtaining a display displayPhoto used for testing.
+ */
+static NSString *const kFakeDisplayPhotoUrl =
+ @"https://www.gstatic.com/images/branding/product/1x/play_apps_48dp.png";
+
+/** @var kFakeDisplayName
+ @brief Fake display name for testing.
+ */
+static NSString *const kFakeDisplayName = @"John GoogleSpeed";
+
+/** @var kFakeEmail
+ @brief Fake email for testing.
+ */
+static NSString *const kFakeEmail =@"firemail@example.com";
+
+/** @var kFakePassword
+ @brief Fake password for testing.
+ */
+static NSString *const kFakePassword =@"fakePassword";
+
+/** @var kInvalidCustomToken
+ @brief The custom token string for testing BYOAuth.
+ */
+static NSString *const kInvalidCustomToken = @"invalid custom token.";
+
+/** @var kSafariGoogleSignOutMessagePrompt
+ @brief The message text informing user to sign-out from Google on safari before continuing.
+ */
+static NSString *const kSafariGoogleSignOutMessagePrompt = @"This automated test assumes that no "
+ "Google account is signed in on Safari, if your are not prompted for a password, sign out on "
+ "Safari and rerun the test.";
+
+/** @var kSafariFacebookSignOutMessagePrompt
+ @brief The message text informing user to sign-out from Facebook on safari before continuing.
+ */
+static NSString *const kSafariFacebookSignOutMessagePrompt = @"This automated test assumes that no "
+ "Facebook account is signed in on Safari, if your are not prompted for a password, sign out on "
+ "Safari and rerun the test.";
+
+/** @var kUnlinkAccountMessagePrompt
+ @brief The message text informing user to use an unlinked account for account linking.
+ */
+static NSString *const kUnlinkAccountMessagePrompt = @"Sign into gmail with an email address "
+ "that has not been linked to this sample application before. Delete account if necessary.";
+
+// Declared extern in .h file.
+NSString *const kCreateUserAccessibilityID = @"CreateUserAccessibilityID";
+
+/** @var kPhoneAuthSectionTitle
+ @brief The title for the phone auth section of the test app.
+ */
+static NSString *const kPhoneAuthSectionTitle = @"Phone Auth";
+
+/** @var kPhoneNumberSignInTitle
+ @brief The title for button to sign in with phone number.
+ */
+static NSString *const kPhoneNumberSignInTitle = @"Sign in With Phone Number";
+
+/** @typedef showEmailPasswordDialogCompletion
+ @brief The type of block which gets called to complete the Email/Password dialog flow.
+ */
+typedef void (^ShowEmailPasswordDialogCompletion)(FIRAuthCredential *credential);
+
+/** @typedef FIRTokenCallback
+ @brief The type of block which gets called when a token is ready.
+ */
+typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error);
+
+/** @brief Method declarations copied from FIRAppInternal.h to avoid importing non-public headers.
+ */
+@interface FIRApp (Internal)
+/** @fn getTokenForcingRefresh:withCallback:
+ @brief Retrieves the Firebase authentication token, possibly refreshing it.
+ @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
+ other than an expiration.
+ @param callback The block to invoke when the token is available.
+ */
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(nonnull FIRTokenCallback)callback;
+@end
+
+@implementation MainViewController {
+ NSMutableString *_consoleString;
+
+ /** @var _authStateDidChangeListeners
+ @brief An array of handles created during calls to @c FIRAuth.addAuthStateDidChangeListener:
+ */
+ NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_authStateDidChangeListeners;
+
+ /** @var _IDTokenDidChangeListeners
+ @brief An array of handles created during calls to @c FIRAuth.addIDTokenDidChangeListener:
+ */
+ NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_IDTokenDidChangeListeners;
+
+ /** @var _userInMemory
+ @brief Acts like the "memory" function of a calculator. An operation allows sample app users
+ to assign this value based on @c FIRAuth.currentUser or clear this value.
+ */
+ FIRUser *_userInMemory;
+
+ /** @var _useUserInMemory
+ @brief Instructs the application to use _userInMemory instead of @c FIRAuth.currentUser for
+ testing operations. This allows us to test if things still work with a user who is not
+ the @c FIRAuth.currentUser, and also allows us to test those things while
+ @c FIRAuth.currentUser remains nil (after a sign-out) and also when @c FIRAuth.currentUser
+ is non-nil (do to a subsequent sign-in.)
+ */
+ BOOL _useUserInMemory;
+}
+
+/** @fn initWithNibName:bundle:
+ @brief Overridden default initializer.
+ */
+- (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+ if (self) {
+ _authStateDidChangeListeners = [NSMutableArray array];
+ _IDTokenDidChangeListeners = [NSMutableArray array];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(authStateChangedForAuth:)
+ name:FIRAuthStateDidChangeNotification
+ object:nil];
+ self.useStatusBarSpinner = YES;
+#if INTERNAL_GOOGLE3_BUILD
+ // Trigger automatic token refresh.
+ [[FIRDatabase database] reference];
+#endif
+ }
+ return self;
+}
+
+- (void)viewDidLoad {
+ // Give us a circle for the image view:
+ _userInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
+ _userInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
+ _userInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
+ _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
+ _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
+ _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
+ [self updateTable];
+ [self updateUserInfo];
+}
+
+- (void)updateTable {
+ __weak typeof(self) weakSelf = self;
+ _tableViewManager.contents =
+ [StaticContentTableViewContent contentWithSections:@[
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleUserDetails cells:@[
+ [StaticContentTableViewCell cellWithCustomCell:_userInfoTableViewCell action:^{
+ [weakSelf presentUserInfo];
+ }],
+ [StaticContentTableViewCell cellWithCustomCell:_userToUseCell],
+ [StaticContentTableViewCell cellWithCustomCell:_userInMemoryInfoTableViewCell action:^{
+ [weakSelf presentUserInMemoryInfo];
+ }],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleSettings cells:@[
+ [StaticContentTableViewCell cellWithTitle:kSettingsButtonText
+ action:^{ [weakSelf presentSettings]; }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kPhoneAuthSectionTitle cells:@[
+ [StaticContentTableViewCell cellWithTitle:kPhoneNumberSignInTitle
+ action:^{ [weakSelf signInWithPhoneNumber]; }],
+ [StaticContentTableViewCell cellWithTitle:kUpdatePhoneNumber
+ action:^{ [weakSelf updatePhoneNumber]; }],
+ [StaticContentTableViewCell cellWithTitle:kLinkPhoneNumber
+ action:^{ [weakSelf linkPhoneNumber]; }],
+ [StaticContentTableViewCell cellWithTitle:kUnlinkPhoneNumber
+ action:^{
+ [weakSelf unlinkFromProvider:FIRPhoneAuthProviderID];
+ }],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleSignIn cells:@[
+ [StaticContentTableViewCell cellWithTitle:kCreateUserTitle
+ value:nil
+ action:^{ [weakSelf createUser]; }
+ accessibilityID:kCreateUserAccessibilityID],
+ [StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText
+ action:^{ [weakSelf signInGoogle]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText
+ action:^{ [weakSelf signInGoogleAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInFacebookButtonText
+ action:^{ [weakSelf signInFacebook]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInFacebookAndRetrieveDataButtonText
+ action:^{ [weakSelf signInFacebookAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInEmailPasswordButtonText
+ action:^{ [weakSelf signInEmailPassword]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInWithCustomTokenButtonText
+ action:^{ [weakSelf signInWithCustomToken]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignInAnonymouslyButtonText
+ action:^{ [weakSelf signInAnonymously]; }],
+ [StaticContentTableViewCell cellWithTitle:kGitHubSignInButtonText
+ action:^{ [weakSelf signInWithGitHub]; }],
+ [StaticContentTableViewCell cellWithTitle:kSignOutButtonText
+ action:^{ [weakSelf signOut]; }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleUserActions cells:@[
+ [StaticContentTableViewCell cellWithTitle:kSetDisplayNameTitle
+ action:^{ [weakSelf setDisplayName]; }],
+ [StaticContentTableViewCell cellWithTitle:kSetPhotoURLText
+ action:^{ [weakSelf setPhotoURL]; }],
+ [StaticContentTableViewCell cellWithTitle:kReloadText
+ action:^{ [weakSelf reloadUser]; }],
+ [StaticContentTableViewCell cellWithTitle:kGetProvidersForEmail
+ action:^{ [weakSelf getProvidersForEmail]; }],
+ [StaticContentTableViewCell cellWithTitle:kRequestVerifyEmail
+ action:^{ [weakSelf requestVerifyEmail]; }],
+ [StaticContentTableViewCell cellWithTitle:kRequestPasswordReset
+ action:^{ [weakSelf requestPasswordReset]; }],
+ [StaticContentTableViewCell cellWithTitle:kResetPassword
+ action:^{ [weakSelf resetPassword]; }],
+ [StaticContentTableViewCell cellWithTitle:kCheckActionCode
+ action:^{ [weakSelf checkActionCode]; }],
+ [StaticContentTableViewCell cellWithTitle:kApplyActionCode
+ action:^{ [weakSelf applyActionCode]; }],
+ [StaticContentTableViewCell cellWithTitle:kVerifyPasswordResetCode
+ action:^{ [weakSelf verifyPasswordResetCode]; }],
+ [StaticContentTableViewCell cellWithTitle:kUpdateEmailText
+ action:^{ [weakSelf updateEmail]; }],
+ [StaticContentTableViewCell cellWithTitle:kUpdatePasswordText
+ action:^{ [weakSelf updatePassword]; }],
+ [StaticContentTableViewCell cellWithTitle:kDeleteUserText
+ action:^{ [weakSelf deleteAccount]; }],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleReauthenticate cells:@[
+ [StaticContentTableViewCell cellWithTitle:kReauthenticateGoogleText
+ action:^{ [weakSelf reauthenticateGoogle]; }],
+ [StaticContentTableViewCell
+ cellWithTitle:kReauthenticateGoogleAndRetrieveDataText
+ action:^{ [weakSelf reauthenticateGoogleAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kReauthenticateFBText
+ action:^{ [weakSelf reauthenticateFB]; }],
+ [StaticContentTableViewCell cellWithTitle:kReauthenticateFBAndRetrieveDataText
+ action:^{ [weakSelf reauthenticateFBAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kReauthenticateEmailText
+ action:^{ [weakSelf reauthenticateEmailPassword]; }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleTokenActions cells:@[
+ [StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
+ action:^{ [weakSelf getUserTokenWithForce:NO]; }],
+ [StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
+ action:^{ [weakSelf getUserTokenWithForce:YES]; }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleLinkUnlinkAccounts cells:@[
+ [StaticContentTableViewCell cellWithTitle:kLinkWithGoogleText
+ action:^{ [weakSelf linkWithGoogle]; }],
+ [StaticContentTableViewCell cellWithTitle:kLinkWithGoogleAndRetrieveDataText
+ action:^{ [weakSelf linkWithGoogleAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kLinkWithFacebookText
+ action:^{ [weakSelf linkWithFacebook]; }],
+ [StaticContentTableViewCell cellWithTitle:kLinkWithFacebookAndRetrieveDataText
+ action:^{ [weakSelf linkWithFacebookAndRetrieveData]; }],
+ [StaticContentTableViewCell cellWithTitle:kLinkWithEmailPasswordText
+ action:^{ [weakSelf linkWithEmailPassword]; }],
+ [StaticContentTableViewCell cellWithTitle:kUnlinkFromGoogle
+ action:^{
+ [weakSelf unlinkFromProvider:FIRGoogleAuthProviderID];
+ }],
+ [StaticContentTableViewCell cellWithTitle:kUnlinkFromFacebook
+ action:^{
+ [weakSelf unlinkFromProvider:FIRFacebookAuthProviderID];
+ }],
+ [StaticContentTableViewCell cellWithTitle:kUnlinkFromEmailPassword
+ action:^{
+ [weakSelf unlinkFromProvider:FIREmailAuthProviderID];
+ }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleApp cells:@[
+ [StaticContentTableViewCell cellWithTitle:kDeleteAppTitle
+ action:^{ [weakSelf deleteApp]; }],
+ [StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
+ action:^{ [weakSelf getAppTokenWithForce:NO]; }],
+ [StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
+ action:^{ [weakSelf getAppTokenWithForce:YES]; }]
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleListeners cells:@[
+ [StaticContentTableViewCell cellWithTitle:kAddAuthStateListenerTitle
+ action:^{ [weakSelf addAuthStateListener]; }],
+ [StaticContentTableViewCell cellWithTitle:kRemoveAuthStateListenerTitle
+ action:^{ [weakSelf removeAuthStateListener]; }],
+ [StaticContentTableViewCell cellWithTitle:kAddIDTokenListenerTitle
+ action:^{ [weakSelf addIDTokenListener]; }],
+ [StaticContentTableViewCell cellWithTitle:kRemoveIDTokenListenerTitle
+ action:^{ [weakSelf removeIDTokenListener]; }],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:kSectionTitleManualTests cells:@[
+ [StaticContentTableViewCell cellWithTitle:kAutoBYOAuthTitle
+ action:^{ [weakSelf automatedBYOAuth]; }],
+ [StaticContentTableViewCell cellWithTitle:kAutoSignInGoogle
+ action:^{ [weakSelf automatedSignInGoogle]; }],
+ [StaticContentTableViewCell cellWithTitle:kAutoSignInFacebook
+ action:^{ [weakSelf automatedSignInFacebook]; }],
+ [StaticContentTableViewCell cellWithTitle:kAutoSignUpEmailPassword
+ action:^{ [weakSelf automatedEmailSignUp]; }],
+ [StaticContentTableViewCell cellWithTitle:kAutoSignInAnonymously
+ action:^{ [weakSelf automatedAnonymousSignIn]; }],
+ [StaticContentTableViewCell cellWithTitle:kAutoAccountLinking
+ action:^{ [weakSelf automatedAccountLinking]; }]
+ ]]
+ ]];
+}
+
+#pragma mark - Interface Builder Actions
+
+- (IBAction)userToUseDidChange:(UISegmentedControl *)sender {
+ _useUserInMemory = (sender.selectedSegmentIndex == 1);
+}
+
+- (IBAction)memoryPlus {
+ _userInMemory = [FIRAuth auth].currentUser;
+ [self updateUserInfo];
+}
+
+- (IBAction)memoryClear {
+ _userInMemory = nil;
+ [self updateUserInfo];
+}
+
+#pragma mark - Actions
+
+/** @fn signInWithProvider:provider:
+ @brief Perform sign in with credential operataion, for given auth provider.
+ @provider The auth provider.
+ @callback The callback to continue the flow which executed this sign-in.
+ */
+- (void)signInWithProvider:(nonnull id<AuthProvider>)provider callback:(void(^)(void))callback {
+ if (!provider) {
+ [self logFailedTest:@"A valid auth provider was not provided to the signInWithProvider."];
+ return;
+ }
+ [provider getAuthCredentialWithPresentingViewController:self
+ callback:^(FIRAuthCredential *credential,
+ NSError *error) {
+ if (!credential) {
+ [self logFailedTest:@"The test needs a valid credential to continue."];
+ return;
+ }
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with provider failed" error:error];
+ [self logFailedTest:@"Sign-in should succeed"];
+ return;
+ } else {
+ [self logSuccess:@"sign-in with provider succeeded."];
+ callback();
+ }
+ }];
+ }];
+}
+
+/** @fn automatedSignInGoogle
+ @brief Automatically executes the manual test for sign-in with Google.
+ */
+- (void)automatedSignInGoogle {
+ [self showMessagePromptWithTitle:kAutoSignInGoogle
+ message:kSafariGoogleSignOutMessagePrompt
+ showCancelButton:NO
+ completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [auth signOut:NULL];
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR GOOGLE SIGN IN:"];
+ [self signInWithProvider:[AuthProviders google] callback:^{
+ [self logSuccess:@"sign-in with Google provider succeeded."];
+ [auth signOut:NULL];
+ [self signInWithProvider:[AuthProviders google] callback:^{
+ [self logSuccess:@"sign-in with Google provider succeeded."];
+ [self updateEmailPasswordWithCompletion:^{
+ [self automatedSignInGoogleDisplayNamePhotoURL];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn automatedSignInGoogleDisplayNamePhotoURL
+ @brief Automatically executes the manual test for setting email and password for sign in with
+ Google.
+ */
+- (void)automatedSignInGoogleDisplayNamePhotoURL {
+ [self signInWithProvider:[AuthProviders google] callback:^{
+ [self updateDisplayNameAndPhotoURlWithCompletion:^{
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH GOOGlE."];
+ [self reloadUser];
+ }];
+ }];
+}
+
+/** @fn automatedSignInFacebook
+ @brief Automatically executes the manual test for sign-in with Facebook.
+ */
+- (void)automatedSignInFacebook {
+ [self showMessagePromptWithTitle:kAutoSignInFacebook
+ message:kSafariFacebookSignOutMessagePrompt
+ showCancelButton:NO
+ completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [auth signOut:NULL];
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR FACEBOOK SIGN IN:"];
+ [self signInWithProvider:[AuthProviders facebook] callback:^{
+ [self logSuccess:@"sign-in with Facebook provider succeeded."];
+ [auth signOut:NULL];
+ [self signInWithProvider:[AuthProviders facebook] callback:^{
+ [self logSuccess:@"sign-in with Facebook provider succeeded."];
+ [self updateEmailPasswordWithCompletion:^{
+ [self automatedSignInFacebookDisplayNamePhotoURL];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn automatedEmailSignUp
+ @brief Automatically executes the manual test for sign-up with email/password.
+ */
+- (void)automatedEmailSignUp {
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR FACEBOOK SIGN IN:"];
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [self signUpNewEmail:kFakeEmail password:kFakePassword callback:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailedTest: @" Email/Password Account account creation failed"];
+ return;
+ }
+ [auth signOut:NULL];
+ FIRAuthCredential *credential =
+ [FIREmailPasswordAuthProvider credentialWithEmail:kFakeEmail
+ password:kFakePassword];
+ [auth signInWithCredential:credential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with Email/Password failed" error:error];
+ [self logFailedTest:@"sign-in with Email/Password should succeed."];
+ return;
+ }
+ [self logSuccess:@"sign-in with Email/Password succeeded."];
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH EMAIL/PASSWORD."];
+ // Delete the user so that we can reuse the fake email address for subsequent tests.
+ [auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"Failed to delete user" error:error];
+ [self logFailedTest:@"Deleting a user that was recently signed-in should succeed."];
+ return;
+ }
+ [self logSuccess:@"User deleted."];
+ }];
+ }];
+ }];
+}
+
+/** @fn automatedAnonymousSignIn
+ @brief Automatically executes the manual test for sign-in anonymously.
+ */
+- (void)automatedAnonymousSignIn {
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR ANONYMOUS SIGN IN:"];
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [auth signOut:NULL];
+ [self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (user) {
+ NSString *anonymousUID = user.uid;
+ [self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (![user.uid isEqual:anonymousUID]) {
+ [self logFailedTest:@"Consecutive anonymous sign-ins should yeild the same User ID"];
+ return;
+ }
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR ANONYMOUS SIGN IN."];
+ }];
+ }
+ }];
+}
+
+/** @fn signInAnonymouslyWithCallback:
+ @brief Performs anonymous sign in and then executes callback.
+ @param callback The callback to be executed.
+ */
+- (void)signInAnonymouslyWithCallback:(nullable FIRAuthResultCallback)callback {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [auth signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in anonymously failed" error:error];
+ [self logFailedTest:@"Recently signed out user should be able to sign in anonymously."];
+ return;
+ }
+ [self logSuccess:@"sign-in anonymously succeeded."];
+ if (callback) {
+ callback(user, nil);
+ }
+ }];
+}
+
+/** @fn automatedAccountLinking
+ @brief Automatically executes the manual test for account linking.
+ */
+- (void)automatedAccountLinking {
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR ACCOUNT LINKING:"];
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ [self logFailedTest:@"Could not obtain auth object."];
+ return;
+ }
+ [auth signOut:NULL];
+ [self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (user) {
+ NSString *anonymousUID = user.uid;
+ [self showMessagePromptWithTitle:@"Sign In Instructions"
+ message:kUnlinkAccountMessagePrompt
+ showCancelButton:NO
+ completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ [[AuthProviders google]
+ getAuthCredentialWithPresentingViewController:self
+ callback:^(FIRAuthCredential *credential,
+ NSError *error) {
+ if (credential) {
+ [user linkWithCredential:credential completion:^(FIRUser *user, NSError *error) {
+ if (error) {
+ [self logFailure:@"link auth provider failed" error:error];
+ [self logFailedTest:@"Account needs to be linked to complete the test."];
+ return;
+ }
+ [self logSuccess:@"link auth provider succeeded."];
+ if (user.isAnonymous) {
+ [self logFailure:@"link auth provider failed, user still anonymous" error:error];
+ [self logFailedTest:@"Account needs to be linked to complete the test."];
+ }
+ if (![user.uid isEqual:anonymousUID]){
+ [self logFailedTest:@"link auth provider failed, UID's are different. Make sure "
+ "you link an account that has NOT been Linked nor Signed-In before."];
+ return;
+ }
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR ACCOUNT LINKING."];
+ }];
+ }
+ }];
+ }];
+ }
+ }];
+}
+
+/** @fn automatedSignInFacebookDisplayNamePhotoURL
+ @brief Automatically executes the manual test for setting email and password for sign-in with
+ Facebook.
+ */
+- (void)automatedSignInFacebookDisplayNamePhotoURL {
+ [self signInWithProvider:[AuthProviders facebook] callback:^{
+ [self updateDisplayNameAndPhotoURlWithCompletion:^{
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH FACEBOOK."];
+ [self reloadUser];
+ }];
+ }];
+}
+
+/** @fn automatedBYOauth
+ @brief Automatically executes the manual test for BYOAuth.
+ */
+- (void)automatedBYOAuth {
+ [self log:@"INITIATING AUTOMATED MANUAL TEST FOR BYOAUTH:"];
+ [self showSpinner:^{
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ NSString *expiredCustomToken =
+ [NSString stringWithContentsOfURL:[NSURL URLWithString:kExpiredCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ [self hideSpinner:^{
+ if (error) {
+ [self log:@"There was an error retrieving the custom token."];
+ return;
+ }
+ FIRAuth *auth = [FIRAuth auth];
+ [auth signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ [self logFailedTest:@"A fresh custom token should succeed in signing-in."];
+ return;
+ }
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [auth.currentUser getIDTokenForcingRefresh:NO
+ completion:^(NSString *_Nullable token,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"refresh token failed" error:error];
+ [self logFailedTest:@"Refresh token should be available."];
+ return;
+ }
+ [self logSuccess:@"refresh token succeeded."];
+ [auth signOut:NULL];
+ [auth signInWithCustomToken:expiredCustomToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (!error) {
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [self logFailedTest:@"sign-in with an expired custom token should NOT succeed."];
+ return;
+ }
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ [auth signInWithCustomToken:kInvalidCustomToken
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (!error) {
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [self logFailedTest:@"sign-in with an invalid custom token should NOT succeed."];
+ return;
+ }
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ //next step of automated test.
+ [self automatedBYOAuthEmailPassword];
+ }];
+ }];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn automatedBYOAuthEmailPassword
+ @brief Automatically executes the manual test for setting email and password in BYOAuth.
+ */
+- (void)automatedBYOAuthEmailPassword {
+ [self showSpinner:^{
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ [self hideSpinner:^{
+ if (error) {
+ [self log:@"There was an error retrieving the custom token."];
+ return;
+ }
+ [[FIRAuth auth] signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ [self logFailedTest:@"A fresh custom token should succeed in signing-in."];
+ return;
+ }
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [self updateEmailPasswordWithCompletion:^{
+ [self automatedBYOAuthDisplayNameAndPhotoURl];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn automatedBYOAuthDisplayNameAndPhotoURl
+ @brief Automatically executes the manual test for setting display name and photo url in BYOAuth.
+ */
+- (void)automatedBYOAuthDisplayNameAndPhotoURl {
+ [self showSpinner:^{
+ NSError *error;
+ NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
+ encoding:NSUTF8StringEncoding
+ error:&error];
+ [self hideSpinner:^{
+ if (error) {
+ [self log:@"There was an error retrieving the custom token."];
+ return;
+ }
+ [[FIRAuth auth] signInWithCustomToken:customToken
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ [self logFailedTest:@"A fresh custom token should succeed in signing-in."];
+ return;
+ }
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [self updateDisplayNameAndPhotoURlWithCompletion:^{
+ [self log:@"FINISHED AUTOMATED MANUAL TEST FOR BYOAUTH."];
+ [self reloadUser];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn updateEmailPasswordWithCompletion:
+ @brief Updates email and password for automatic manual tests, and signs user in with new email
+ and password.
+ @param completion The completion block to continue the automatic test flow.
+ */
+- (void)updateEmailPasswordWithCompletion:(void(^)(void))completion {
+ FIRAuth *auth = [FIRAuth auth];
+ [auth.currentUser updateEmail:kFakeEmail completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"update email failed" error:error];
+ [self logFailedTest:@"Update email should succeed when properly signed-in."];
+ return;
+ }
+ [self logSuccess:@"update email succeeded."];
+ [auth.currentUser updatePassword:kFakePassword completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"update password failed" error:error];
+ [self logFailedTest:@"Update password should succeed when properly signed-in."];
+ return;
+ }
+ [self logSuccess:@"update password succeeded."];
+ [auth signOut:NULL];
+ FIRAuthCredential *credential =
+ [FIREmailAuthProvider credentialWithEmail:kFakeEmail password:kFakePassword];
+ [auth signInWithCredential:credential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with Email/Password failed" error:error];
+ [self logFailedTest:@"sign-in with Email/Password should succeed."];
+ return;
+ }
+ [self logSuccess:@"sign-in with Email/Password succeeded."];
+ // Delete the user so that we can reuse the fake email address for subsequent tests.
+ [auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"Failed to delete user." error:error];
+ [self logFailedTest:@"Deleting a user that was recently signed-in should succeed"];
+ return;
+ }
+ [self logSuccess:@"User deleted."];
+ completion();
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn updateDisplayNameAndPhotoURlWithCompletion:
+ @brief Automatically executes the manual test for setting displayName and photoUrl.
+ @param completion The completion block to continue the automatic test flow.
+ */
+- (void)updateDisplayNameAndPhotoURlWithCompletion:(void(^)(void))completion {
+ FIRAuth *auth = [FIRAuth auth];
+ FIRUserProfileChangeRequest *changeRequest = [auth.currentUser profileChangeRequest];
+ changeRequest.photoURL = [NSURL URLWithString:kFakeDisplayPhotoUrl];
+ [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"set photo URL failed" error:error];
+ [self logFailedTest:@"Change photo Url should succeed when signed-in."];
+ return;
+ }
+ [self logSuccess:@"set PhotoURL succeeded."];
+ FIRUserProfileChangeRequest *changeRequest = [auth.currentUser profileChangeRequest];
+ changeRequest.displayName = kFakeDisplayName;
+ [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"set display name failed" error:error];
+ [self logFailedTest:@"Change display name should succeed when signed-in."];
+ return;
+ }
+ [self logSuccess:@"set display name succeeded."];
+ completion();
+ }];
+ }];
+}
+
+/** @fn addAuthStateListener
+ @brief Adds an auth state did change listener (block).
+ */
+- (void)addAuthStateListener {
+ __weak typeof(self) weakSelf = self;
+ NSUInteger index = _authStateDidChangeListeners.count;
+ [self log:[NSString stringWithFormat:@"Auth State Did Change Listener #%lu was added.",
+ (unsigned long)index]];
+ FIRAuthStateDidChangeListenerHandle handle =
+ [[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth,
+ FIRUser *_Nullable user) {
+ [weakSelf log:[NSString stringWithFormat:
+ @"Auth State Did Change Listener #%lu was invoked on user '%@'.",
+ (unsigned long)index, user.uid]];
+ }];
+ [_authStateDidChangeListeners addObject:handle];
+}
+
+/** @fn removeAuthStateListener
+ @brief Removes an auth state did change listener (block).
+ */
+- (void)removeAuthStateListener {
+ if (!_authStateDidChangeListeners.count) {
+ [self log:@"No remaining Auth State Did Change Listeners."];
+ return;
+ }
+ NSUInteger index = _authStateDidChangeListeners.count - 1;
+ FIRAuthStateDidChangeListenerHandle handle = _authStateDidChangeListeners.lastObject;
+ [[FIRAuth auth] removeAuthStateDidChangeListener:handle];
+ [_authStateDidChangeListeners removeObject:handle];
+ NSString *logString =
+ [NSString stringWithFormat:@"Auth State Did Change Listener #%lu was removed.",
+ (unsigned long)index];
+ [self log:logString];
+}
+
+/** @fn addIDTokenListener
+ @brief Adds an ID token did change listener (block).
+ */
+- (void)addIDTokenListener {
+ __weak typeof(self) weakSelf = self;
+ NSUInteger index = _IDTokenDidChangeListeners.count;
+ [self log:[NSString stringWithFormat:@"ID Token Did Change Listener #%lu was added.",
+ (unsigned long)index]];
+ FIRIDTokenDidChangeListenerHandle handle =
+ [[FIRAuth auth] addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth,
+ FIRUser *_Nullable user) {
+ [weakSelf log:[NSString stringWithFormat:
+ @"ID Token Did Change Listener #%lu was invoked on user '%@'.",
+ (unsigned long)index, user.uid]];
+ }];
+ [_IDTokenDidChangeListeners addObject:handle];
+}
+
+/** @fn removeIDTokenListener
+ @brief Removes an ID token did change listener (block).
+ */
+- (void)removeIDTokenListener {
+ if (!_IDTokenDidChangeListeners.count) {
+ [self log:@"No remaining ID Token Did Change Listeners."];
+ return;
+ }
+ NSUInteger index = _IDTokenDidChangeListeners.count - 1;
+ FIRIDTokenDidChangeListenerHandle handle = _IDTokenDidChangeListeners.lastObject;
+ [[FIRAuth auth] removeIDTokenDidChangeListener:handle];
+ [_IDTokenDidChangeListeners removeObject:handle];
+ NSString *logString =
+ [NSString stringWithFormat:@"ID Token Did Change Listener #%lu was removed.",
+ (unsigned long)index];
+ [self log:logString];
+}
+
+/** @fn log:
+ @brief Prints a log message to the sample app console.
+ @param string The string to add to the console.
+ */
+- (void)log:(NSString *)string {
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
+ NSString *date = [dateFormatter stringFromDate:[NSDate date]];
+ if (!_consoleString) {
+ _consoleString = [NSMutableString string];
+ }
+ [_consoleString appendString:[NSString stringWithFormat:@"%@ %@\n", date, string]];
+ _consoleTextView.text = _consoleString;
+
+ CGRect targetRect = CGRectMake(0, _consoleTextView.contentSize.height - 1, 1, 1);
+ [_consoleTextView scrollRectToVisible:targetRect animated:YES];
+}
+
+/** @fn logSuccess:
+ @brief Wraps a string into a succeful log message format.
+ @param string Part of the log message.
+ @remarks The @string parameter should be a string ending with a period, as it is the end of the
+ log message.
+ */
+- (void)logSuccess:(NSString *)string {
+ [self log:[NSString stringWithFormat:@"SUCCESS: %@", string]];
+}
+
+/** @fn logFailure:
+ @brief Wraps a string into a failed log message format.
+ @param string Part of the message to wrap.
+ @remarks The @string parameter should be a string that NEVER ends with a period, as it is
+ guaranteed not to be the last part fo the log message.
+ */
+- (void)logFailure:(NSString *)string error:(NSError *) error {
+ NSString *message =
+ [NSString stringWithFormat:@"FAILURE: %@ Error Description: %@.", string, error.description];
+ [self log:message];
+}
+
+/** @fn logTestFailed
+ @brief Logs test failure to the console.
+ @param reason The reason why the test is considered a failure.
+ @remarks The calling method should immediately terminate after invoking this method i.e by
+ return statement or end of fucntions. The @reason parameter should be a string ending with a
+ period, as it is the end of the log message.
+ */
+- (void)logFailedTest:( NSString *_Nonnull )reason {
+ [self log:[NSString stringWithFormat:@"FAILIURE: TEST FAILED - %@", reason]];
+}
+
+/** @fn presentSettings
+ @brief Invoked when the settings row is pressed.
+ */
+- (void)presentSettings {
+ SettingsViewController *settingsViewController = [[SettingsViewController alloc]
+ initWithNibName:NSStringFromClass([SettingsViewController class])
+ bundle:nil];
+ [self presentViewController:settingsViewController animated:YES completion:nil];
+}
+
+/** @fn presentUserInfo
+ @brief Invoked when the user info row is pressed.
+ */
+- (void)presentUserInfo {
+ UserInfoViewController *userInfoViewController =
+ [[UserInfoViewController alloc] initWithUser:[FIRAuth auth].currentUser];
+ [self presentViewController:userInfoViewController animated:YES completion:nil];
+}
+
+/** @fn presentUserInMemoryInfo
+ @brief Invoked when the user in memory info row is pressed.
+ */
+- (void)presentUserInMemoryInfo {
+ UserInfoViewController *userInfoViewController =
+ [[UserInfoViewController alloc] initWithUser:_userInMemory];
+ [self presentViewController:userInfoViewController animated:YES completion:nil];
+}
+
+/** @fn signInGoogle
+ @brief Invoked when "Sign in with Google" row is pressed.
+ */
+- (void)signInGoogle {
+ [self signinWithProvider:[AuthProviders google] retrieveData:NO];
+}
+
+/** @fn signInGoogleAndRetrieveData
+ @brief Invoked when "Sign in with Google and retrieve data" row is pressed.
+ */
+- (void)signInGoogleAndRetrieveData {
+ [self signinWithProvider:[AuthProviders google] retrieveData:YES];
+}
+
+/** @fn signInFacebook
+ @brief Invoked when "Sign in with Facebook" row is pressed.
+ */
+- (void)signInFacebook {
+ [self signinWithProvider:[AuthProviders facebook] retrieveData:NO];
+}
+
+/** @fn signInFacebookAndRetrieveData
+ @brief Invoked when "Sign in with Facebook and retrieve data" row is pressed.
+ */
+- (void)signInFacebookAndRetrieveData {
+ [self signinWithProvider:[AuthProviders facebook] retrieveData:YES];
+}
+
+/** @fn signInEmailPassword
+ @brief Invoked when "sign in with Email/Password" row is pressed.
+ */
+- (void)signInEmailPassword {
+ [self showTextInputPromptWithMessage:@"Email Address:"
+ keyboardType:UIKeyboardTypeEmailAddress
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
+ if (!userPressedOK || !email.length) {
+ return;
+ }
+ [self showTextInputPromptWithMessage:@"Password:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
+ if (!userPressedOK) {
+ return;
+ }
+ FIRAuthCredential *credential =
+ [FIREmailAuthProvider credentialWithEmail:email
+ password:password];
+ [self showSpinner:^{
+ [[FIRAuth auth] signInWithCredential:credential
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"sign-in with Email/Password failed" error:error];
+ } else {
+ [self logSuccess:@"sign-in with Email/Password succeeded."];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
+ }];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn signUpNewEmail
+ @brief Invoked if sign-in is attempted with new email/password.
+ @remarks Should only be called if @c FIRAuthErrorCodeInvalidEmail is encountered on attepmt to
+ sign in with email/password.
+ */
+- (void)signUpNewEmail:(NSString *)email
+ password:(NSString *)password
+ callback:(nullable FIRAuthResultCallback)callback {
+ [[FIRAuth auth] createUserWithEmail:email
+ password:password
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-up with Email/Password failed" error:error];
+ if (callback) {
+ callback(nil, error);
+ }
+ } else {
+ [self logSuccess:@"sign-up with Email/Password succeeded."];
+ if (callback) {
+ callback(user, nil);
+ }
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
+ }];
+}
+
+/** @fn signInWithCustomToken
+ @brief Signs the user in using a manually-entered custom token.
+ */
+- (void)signInWithCustomToken {
+ CustomTokenDataEntryViewControllerCompletion action =
+ ^(BOOL cancelled, NSString *_Nullable userEnteredTokenText) {
+ if (cancelled) {
+ [self log:@"CANCELLED:sign-in with custom token cancelled."];
+ return;
+ }
+
+ [self doSignInWithCustomToken:userEnteredTokenText];
+ };
+ CustomTokenDataEntryViewController *dataEntryViewController =
+ [[CustomTokenDataEntryViewController alloc] initWithCompletion:action];
+ [self presentViewController:dataEntryViewController animated:YES completion:nil];
+}
+
+/** @fn signOut
+ @brief Signs the user out.
+ */
+- (void)signOut {
+ [[AuthProviders google] signOut];
+ [[AuthProviders facebook] signOut];
+ [[FIRAuth auth] signOut:NULL];
+}
+
+/** @fn deleteAccount
+ @brief Deletes the current user account and signs the user out.
+ */
+- (void)deleteAccount {
+ FIRUser *user = [self user];
+ [user deleteWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"delete account failed" error:error];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:kDeleteUserText error:error];
+ }];
+}
+
+/** @fn reauthenticateGoogle
+ @brief Asks the user to reauthenticate with Google.
+ */
+- (void)reauthenticateGoogle {
+ [self reauthenticate:[AuthProviders google] retrieveData:NO];
+}
+
+/** @fn reauthenticateGoogleAndRetrieveData
+ @brief Asks the user to reauthenticate with Google and retrieve additional data.
+ */
+- (void)reauthenticateGoogleAndRetrieveData {
+ [self reauthenticate:[AuthProviders google] retrieveData:YES];
+}
+
+/** @fn reauthenticateFB
+ @brief Asks the user to reauthenticate with Facebook.
+ */
+- (void)reauthenticateFB {
+ [self reauthenticate:[AuthProviders facebook] retrieveData:NO];
+}
+
+/** @fn reauthenticateFBAndRetrieveData
+ @brief Asks the user to reauthenticate with Facebook and retrieve additional data.
+ */
+- (void)reauthenticateFBAndRetrieveData {
+ [self reauthenticate:[AuthProviders facebook] retrieveData:YES];
+}
+
+/** @fn reauthenticateEmailPassword
+ @brief Asks the user to reauthenticate with email/password.
+ */
+- (void)reauthenticateEmailPassword {
+ FIRUser *user = [self user];
+ if (!user) {
+ NSString *title = @"Missing User";
+ NSString *message = @"There is no signed-in email/password user.";
+ [self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil];
+ return;
+ }
+ [self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) {
+ [self showSpinner:^{
+ [[self user] reauthenticateWithCredential:credential
+ completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"reauthicate with email/password failed" error:error];
+ } else {
+ [self logSuccess:@"reauthicate with email/password succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kReauthenticateEmailText error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn reauthenticate:
+ @brief Reauthenticates user.
+ @param authProvider The auth provider to use for reauthentication.
+ @param retrieveData Defines if additional provider data should be read.
+ */
+- (void)reauthenticate:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
+ FIRUser *user = [self user];
+ if (!user) {
+ NSString *provider = @"Firebase";
+ if ([authProvider isKindOfClass:[GoogleAuthProvider class]]) {
+ provider = @"Google";
+ } else if ([authProvider isKindOfClass:[FacebookAuthProvider class]]) {
+ provider = @"Facebook";
+ }
+ NSString *title = @"Missing User";
+ NSString *message =
+ [NSString stringWithFormat:@"There is no signed-in %@ user.", provider];
+ [self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil];
+ return;
+ }
+ [authProvider getAuthCredentialWithPresentingViewController:self
+ callback:^(FIRAuthCredential *credential,
+ NSError *error) {
+ if (credential) {
+ FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"reauthenticate operation failed" error:error];
+ } else {
+ [self logSuccess:@"reauthenticate operation succeeded."];
+ }
+ if (authResult.additionalUserInfo) {
+ [self logSuccess:
+ [NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate" error:error];
+ };
+ FIRUserProfileChangeCallback callback = ^(NSError *_Nullable error) {
+ completion(nil, error);
+ };
+ if (retrieveData) {
+ [user reauthenticateAndRetrieveDataWithCredential:credential completion:completion];
+ } else {
+ [user reauthenticateWithCredential:credential completion:callback];
+ }
+ }
+ }];
+}
+
+/** @fn signinWithProvider:
+ @brief Signs in the user with provided auth provider.
+ @param authProvider The auth provider to use for sign-in.
+ @param retrieveData Defines if additional provider data should be read.
+ */
+- (void)signinWithProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
+ FIRAuth *auth = [FIRAuth auth];
+ if (!auth) {
+ return;
+ }
+ [authProvider getAuthCredentialWithPresentingViewController:self
+ callback:^(FIRAuthCredential *credential,
+ NSError *error) {
+ if (credential) {
+ FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with provider failed" error:error];
+ } else {
+ [self logSuccess:@"sign-in with provider succeeded."];
+ }
+ if (authResult.additionalUserInfo) {
+ [self logSuccess:
+ [NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
+ };
+ FIRAuthResultCallback callback = ^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ completion(nil, error);
+ };
+ if (retrieveData) {
+ [auth signInAndRetrieveDataWithCredential:credential completion:completion];
+ } else {
+ [auth signInWithCredential:credential completion:callback];
+ }
+ }
+ }];
+}
+
+/** @fn tokenCallback
+ @return A callback block to show the token.
+ */
+- (FIRAuthTokenCallback)tokenCallback {
+ return ^(NSString *_Nullable token, NSError *_Nullable error) {
+ if (error) {
+ [self showMessagePromptWithTitle:kTokenRefreshErrorAlertTitle
+ message:error.localizedDescription
+ showCancelButton:NO
+ completion:nil];
+ [self logFailure:@"refresh token failed" error:error];
+ return;
+ }
+ [self logSuccess:@"refresh token succeeded."];
+ [self showMessagePromptWithTitle:kTokenRefreshedAlertTitle
+ message:token
+ showCancelButton:NO
+ completion:nil];
+ };
+}
+
+/** @fn getUserTokenWithForce:
+ @brief Gets the token from @c FIRUser , optionally a refreshed one.
+ @param force Whether the refresh is forced or not.
+ */
+- (void)getUserTokenWithForce:(BOOL)force {
+ [[self user] getIDTokenForcingRefresh:force completion:[self tokenCallback]];
+}
+
+/** @fn getAppTokenWithForce:
+ @brief Gets the token from @c FIRApp , optionally a refreshed one.
+ @param force Whether the refresh is forced or not.
+ */
+- (void)getAppTokenWithForce:(BOOL)force {
+ [[FIRApp defaultApp] getTokenForcingRefresh:force withCallback:[self tokenCallback]];
+}
+
+/** @fn setDisplayName
+ @brief Changes the display name of the current user.
+ */
+- (void)setDisplayName {
+ [self showTextInputPromptWithMessage:@"Display Name:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ [self showSpinner:^{
+ FIRUserProfileChangeRequest *changeRequest = [[self user] profileChangeRequest];
+ changeRequest.displayName = userInput;
+ [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"set display name failed" error:error];
+ } else {
+ [self logSuccess:@"set display name succeeded."];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:kSetDisplayNameTitle error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn setPhotoURL
+ @brief Changes the photo url of the current user.
+ */
+- (void)setPhotoURL {
+ [self showTextInputPromptWithMessage:@"Photo URL:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+
+ [self showSpinner:^{
+ FIRUserProfileChangeRequest *changeRequest = [[self user] profileChangeRequest];
+ changeRequest.photoURL = [NSURL URLWithString:userInput];
+ [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"set photo URL failed" error:error];
+ } else {
+ [self logSuccess:@"set Photo URL succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kSetPhotoURLText error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn reloadUser
+ @brief Reloads the user from server.
+ */
+- (void)reloadUser {
+ [self showSpinner:^() {
+ [[self user] reloadWithCompletion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"reload user failed" error:error];
+ } else {
+ [self logSuccess:@"reload user succeeded."];
+ }
+ [self hideSpinner:^() {
+ [self showTypicalUIForUserUpdateResultsWithTitle:kReloadText error:error];
+ }];
+ }];
+ }];
+}
+
+/** @fn linkWithAuthProvider:retrieveData:
+ @brief Asks the user to sign in with an auth provider and link the current user with it.
+ @param authProvider The auth provider to sign in and link with.
+ @param retrieveData Defines if additional provider data should be read.
+ */
+- (void)linkWithAuthProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
+ FIRUser *user = [self user];
+ if (!user) {
+ return;
+ }
+ [authProvider getAuthCredentialWithPresentingViewController:self
+ callback:^(FIRAuthCredential *credential,
+ NSError *error) {
+ if (credential) {
+ FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"link auth provider failed" error:error];
+ } else {
+ [self logSuccess:@"link auth provider succeeded."];
+ }
+ if (authResult.additionalUserInfo) {
+ [self logSuccess:
+ [NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
+ }
+ if (retrieveData) {
+ [self showUIForAuthDataResultWithResult:authResult error:error];
+ } else {
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Link Account" error:error];
+ }
+ };
+ FIRAuthResultCallback callback = ^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ completion(nil, error);
+ };
+ if (retrieveData) {
+ [user linkAndRetrieveDataWithCredential:credential completion:completion];
+ } else {
+ [user linkWithCredential:credential completion:callback];
+ }
+ }
+ }];
+}
+
+/** @fn linkWithGoogle
+ @brief Asks the user to sign in with Google and link the current user with this Google account.
+ */
+- (void)linkWithGoogle {
+ [self linkWithAuthProvider:[AuthProviders google] retrieveData:NO];
+}
+
+/** @fn linkWithGoogleAndRetrieveData
+ @brief Asks the user to sign in with Google and link the current user with this Google account
+ and retrieve additional data.
+ */
+- (void)linkWithGoogleAndRetrieveData {
+ [self linkWithAuthProvider:[AuthProviders google] retrieveData:YES];
+}
+
+/** @fn linkWithFacebook
+ @brief Asks the user to sign in with Facebook and link the current user with this Facebook
+ account.
+ */
+- (void)linkWithFacebook {
+ [self linkWithAuthProvider:[AuthProviders facebook] retrieveData:NO];
+}
+
+/** @fn linkWithFacebookAndRetrieveData
+ @brief Asks the user to sign in with Facebook and link the current user with this Facebook
+ account and retrieve additional data.
+ */
+- (void)linkWithFacebookAndRetrieveData {
+ [self linkWithAuthProvider:[AuthProviders facebook] retrieveData:YES];
+}
+
+/** @fn linkWithEmailPassword
+ @brief Asks the user to sign in with Facebook and link the current user with this Facebook
+ account.
+ */
+- (void)linkWithEmailPassword {
+ [self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) {
+ [self showSpinner:^{
+ [[self user] linkWithCredential:credential
+ completion:^(FIRUser *user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"link Email/Password failed" error:error];
+ } else {
+ [self logSuccess:@"link Email/Password succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kLinkWithEmailPasswordText error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn showEmailPasswordDialogWithCompletion:
+ @brief shows email/password input dialog.
+ @param completion The completion block that will do some operation on the credential email
+ /passwowrd credential obtained.
+ */
+- (void)showEmailPasswordDialogWithCompletion:(ShowEmailPasswordDialogCompletion)completion {
+ [self showTextInputPromptWithMessage:@"Email Address:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
+ if (!userPressedOK || !email.length) {
+ return;
+ }
+ [self showTextInputPromptWithMessage:@"Password:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
+ if (!userPressedOK || !password.length) {
+ return;
+ }
+
+ FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email
+ password:password];
+ completion(credential);
+ }];
+ }];
+}
+
+/** @fn unlinkFromProvider:
+ @brief Unlinks the current user from the provider with the specified provider ID.
+ @param provider The provider ID of the provider to unlink the current user's account from.
+ */
+- (void)unlinkFromProvider:(NSString *)provider {
+ [[self user] unlinkFromProvider:provider
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"unlink auth provider failed" error:error];
+ } else {
+ [self logSuccess:@"unlink auth provider succeeded."];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:kUnlinkTitle error:error];
+ }];
+}
+
+/** @fn getProvidersForEmail
+ @brief Prompts the user for an email address, calls @c FIRAuth.getProvidersForEmail:callback:
+ and displays the result.
+ */
+- (void)getProvidersForEmail {
+ [self showTextInputPromptWithMessage:@"Email:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[FIRAuth auth] fetchProvidersForEmail:userInput
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"get providers for email failed" error:error];
+ } else {
+ [self logSuccess:@"get providers for email succeeded."];
+ }
+ [self hideSpinner:^{
+ if (error) {
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+
+ [self showMessagePrompt:[providers componentsJoinedByString:@", "]];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn requestVerifyEmail
+ @brief Requests a "verify email" email be sent.
+ */
+- (void)requestVerifyEmail {
+ [self showSpinner:^{
+ [[self user] sendEmailVerificationWithCompletion:^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"request verify email failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"request verify email succeeded."];
+ [self showMessagePrompt:@"Sent"];
+ }];
+ }];
+ }];
+}
+
+/** @fn requestPasswordReset
+ @brief Requests a "password reset" email be sent.
+ */
+- (void)requestPasswordReset {
+ [self showTextInputPromptWithMessage:@"Email:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRAuth auth] sendPasswordResetWithEmail:userInput completion:^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"request password reset failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"request password reset succeeded."];
+ [self showMessagePrompt:@"Sent"];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn resetPassword
+ @brief Performs a password reset operation.
+ */
+- (void)resetPassword {
+ [self showTextInputPromptWithMessage:@"OOB Code:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ NSString *code = userInput;
+
+ [self showTextInputPromptWithMessage:@"New Password:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[FIRAuth auth] confirmPasswordResetWithCode:code
+ newPassword:userInput
+ completion:^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"Password reset failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"Password reset succeeded."];
+ [self showMessagePrompt:@"Password reset succeeded."];
+ }];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn checkActionCode
+ @brief Performs a "check action code" operation.
+ */
+- (void)checkActionCode {
+ [self showTextInputPromptWithMessage:@"OOB Code:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRAuth auth] checkActionCode:userInput completion:^(FIRActionCodeInfo *_Nullable info,
+ NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"Check action code failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"Check action code succeeded."];
+ NSString *email = [info dataForKey:FIRActionCodeEmailKey];
+ NSString *operation = [self nameForActionCodeOperation:info.operation];
+ NSString *infoMessage =
+ [[NSString alloc] initWithFormat:@"Email: %@\n Operation: %@", email, operation];
+ [self showMessagePrompt:infoMessage];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn applyActionCode
+ @brief Performs a "apply action code" operation.
+ */
+- (void)applyActionCode {
+ [self showTextInputPromptWithMessage:@"OOB Code:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ [self showSpinner:^{
+
+ [[FIRAuth auth] applyActionCode:userInput completion:^(NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"Apply action code failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"Apply action code succeeded."];
+ [self showMessagePrompt:@"Action code was properly applied."];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn verifyPasswordResetCode
+ @brief Performs a "verify password reset code" operation.
+ */
+- (void)verifyPasswordResetCode {
+ [self showTextInputPromptWithMessage:@"OOB Code:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRAuth auth] verifyPasswordResetCode:userInput completion:^(NSString *_Nullable email,
+ NSError *_Nullable error) {
+ [self hideSpinner:^{
+ if (error) {
+ [self logFailure:@"Verify password reset code failed" error:error];
+ [self showMessagePrompt:error.localizedDescription];
+ return;
+ }
+ [self logSuccess:@"Verify password resest code succeeded."];
+ NSString *alertMessage =
+ [[NSString alloc] initWithFormat:@"Code verified for email: %@", email];
+ [self showMessagePrompt:alertMessage];
+ }];
+ }];
+ }];
+ }];
+}
+
+
+/** @fn nameForActionCodeOperation
+ @brief Returns the string value of the provided FIRActionCodeOperation value.
+ @param operation the FIRActionCodeOperation value to convert to string.
+ @return String conversion of FIRActionCodeOperation value.
+ */
+- (NSString *)nameForActionCodeOperation:(FIRActionCodeOperation)operation {
+ switch (operation) {
+ case FIRActionCodeOperationVerifyEmail:
+ return @"Verify Email";
+ case FIRActionCodeOperationPasswordReset:
+ return @"Password Reset";
+ case FIRActionCodeOperationUnknown:
+ return @"Unknown action";
+ }
+}
+
+/** @fn updateEmail
+ @brief Changes the email address of the current user.
+ */
+- (void)updateEmail {
+ [self showTextInputPromptWithMessage:@"Email Address:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK || !userInput.length) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[self user] updateEmail:userInput completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"update email failed" error:error];
+ } else {
+ [self logSuccess:@"update email succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kUpdateEmailText error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn updatePassword
+ @brief Updates the password of the current user.
+ */
+- (void)updatePassword {
+ [self showTextInputPromptWithMessage:@"New Password:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (!userPressedOK) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[self user] updatePassword:userInput completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"update password failed" error:error];
+ } else {
+ [self logSuccess:@"update password succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kUpdatePasswordText error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn createUser
+ @brief Creates a new user.
+ */
+- (void)createUser {
+ [self showTextInputPromptWithMessage:@"Email:"
+ keyboardType:UIKeyboardTypeEmailAddress
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
+ if (!userPressedOK || !email.length) {
+ return;
+ }
+
+ [self showTextInputPromptWithMessage:@"Password:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
+ if (!userPressedOK) {
+ return;
+ }
+
+ [self showSpinner:^{
+ [[FIRAuth auth] createUserWithEmail:email
+ password:password
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"create user failed" error:error];
+ } else {
+ [self logSuccess:@"create user succeeded."];
+ }
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
+ }];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn signInWithPhoneNumber
+ @brief Allows sign in with phone number.
+ */
+- (void)signInWithPhoneNumber {
+ [self showTextInputPromptWithMessage:@"Phone #:"
+ keyboardType:UIKeyboardTypePhonePad
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
+ if (!userPressedOK || !phoneNumber.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
+ completion:^(NSString *_Nullable verificationID,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"failed to send verification code" error:error];
+ return;
+ }
+ [self logSuccess:@"Code sent"];
+
+ [self showTextInputPromptWithMessage:@"Verification code:"
+ keyboardType:UIKeyboardTypeNumberPad
+ completionBlock:^(BOOL userPressedOK,
+ NSString *_Nullable verificationCode) {
+ if (!userPressedOK || !verificationCode.length) {
+ return;
+ }
+ [self showSpinner:^{
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
+ verificationCode:verificationCode];
+ [[FIRAuth auth] signInWithCredential:credential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"failed to verify phone number" error:error];
+ return;
+ }
+ }];
+ }];
+ }];
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn updatePhoneNumber
+ @brief Allows adding a verified phone number to the currently signed user.
+ */
+- (void)updatePhoneNumber {
+ [self showTextInputPromptWithMessage:@"Phone #:"
+ keyboardType:UIKeyboardTypePhonePad
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
+ if (!userPressedOK || !phoneNumber.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
+ completion:^(NSString *_Nullable verificationID,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"failed to send verification code" error:error];
+ return;
+ }
+ [self logSuccess:@"Code sent"];
+
+ [self showTextInputPromptWithMessage:@"Verification code:"
+ keyboardType:UIKeyboardTypeNumberPad
+ completionBlock:^(BOOL userPressedOK,
+ NSString *_Nullable verificationCode) {
+ if (!userPressedOK || !verificationCode.length) {
+ return;
+ }
+ [self showSpinner:^{
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
+ verificationCode:verificationCode];
+ [[self user] updatePhoneNumberCredential:credential
+ completion:^(NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"update phone number failed" error:error];
+ } else {
+ [self logSuccess:@"update phone number succeeded."];
+ }
+ }];
+ }];
+ }];
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn linkPhoneNumber
+ @brief Allows linking a verified phone number to the currently signed user.
+ */
+- (void)linkPhoneNumber {
+ [self showTextInputPromptWithMessage:@"Phone #:"
+ keyboardType:UIKeyboardTypePhonePad
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
+ if (!userPressedOK || !phoneNumber.length) {
+ return;
+ }
+ [self showSpinner:^{
+ [[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
+ completion:^(NSString *_Nullable verificationID,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"failed to send verification code" error:error];
+ return;
+ }
+ [self logSuccess:@"Code sent"];
+
+ [self showTextInputPromptWithMessage:@"Verification code:"
+ keyboardType:UIKeyboardTypeNumberPad
+ completionBlock:^(BOOL userPressedOK,
+ NSString *_Nullable verificationCode) {
+ if (!userPressedOK || !verificationCode.length) {
+ return;
+ }
+ [self showSpinner:^{
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
+ verificationCode:verificationCode];
+ [[self user] linkWithCredential:credential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) {
+ [self showMessagePromptWithTitle:@"Phone number is already linked to another user"
+ message:@"Tap Ok to sign in with that user now."
+ showCancelButton:YES
+ completion:^(BOOL userPressedOK,
+ NSString *_Nullable userInput) {
+ if (userPressedOK) {
+ // If FIRAuthErrorCodeCredentialAlreadyInUse error, sign in with the provided
+ // credential.
+ FIRPhoneAuthCredential *credential =
+ error.userInfo[FIRAuthUpdatedCredentialKey];
+ [[FIRAuth auth] signInWithCredential:credential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"failed to verify phone number" error:error];
+ return;
+ }
+ }];
+ }
+ }];
+ } else {
+ [self logFailure:@"link phone number failed" error:error];
+ }
+ return;
+ }
+ [self logSuccess:@"link phone number succeeded."];
+ }];
+ }];
+ }];
+ [self hideSpinner:^{
+ [self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
+ }];
+ }];
+ }];
+ }];
+}
+
+/** @fn signInAnonymously
+ @brief Signs in as an anonymous user.
+ */
+- (void)signInAnonymously {
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in anonymously failed" error:error];
+ } else {
+ [self logSuccess:@"sign-in anonymously succeeded."];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:kSignInAnonymouslyButtonText error:error];
+ }];
+}
+
+/** @fn signInWithGitHub
+ @brief Signs in as a GitHub user. Prompts the user for an access token and uses this access
+ token to create a GitHub (generic) credential for signing in.
+ */
+- (void)signInWithGitHub {
+ [self showTextInputPromptWithMessage:@"GitHub Access Token:"
+ completionBlock:^(BOOL userPressedOK, NSString *_Nullable accessToken) {
+ if (!userPressedOK || !accessToken.length) {
+ return;
+ }
+ FIRAuthCredential *credential =
+ [FIROAuthProvider credentialWithProviderID:FIRGitHubAuthProviderID accessToken:accessToken];
+ if (credential) {
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with provider failed" error:error];
+ } else {
+ [self logSuccess:@"sign-in with provider succeeded."];
+ }
+ [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
+ }];
+ }
+ }];
+}
+
+/** @fn deleteApp
+ @brief Deletes the @c FIRApp associated with our @c FIRAuth instance.
+ */
+- (void)deleteApp {
+ [[FIRApp defaultApp] deleteApp:^(BOOL success) {
+ [self log:success ? @"App deleted successfully." : @"Failed to delete app."];
+ }];
+}
+
+#pragma mark - Helpers
+
+/** @fn user
+ @brief The user to use for user operations. Takes into account the "use signed-in user vs. use
+ user in memory" setting.
+ */
+- (FIRUser *)user {
+ return _useUserInMemory ? _userInMemory : [FIRAuth auth].currentUser;
+}
+
+/** @fn showTypicalUIForUserUpdateResultsWithTitle:error:
+ @brief Shows a prompt if error is non-nil with the localized description of the error.
+ @param resultsTitle The title of the prompt
+ @param error The error details to display if non-nil.
+ */
+- (void)showTypicalUIForUserUpdateResultsWithTitle:(NSString *)resultsTitle
+ error:(NSError *)error {
+ if (error) {
+ NSString *message = [NSString stringWithFormat:@"%@ (%ld)\n%@",
+ error.domain,
+ (long)error.code,
+ error.localizedDescription];
+ if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
+ NSString *errorEmail = error.userInfo[FIRAuthErrorUserInfoEmailKey];
+ resultsTitle = [NSString stringWithFormat:@"Existing email : %@", errorEmail];
+ }
+ [self showMessagePromptWithTitle:resultsTitle
+ message:message
+ showCancelButton:NO
+ completion:nil];
+ return;
+ }
+ [self updateUserInfo];
+}
+
+/** @fn showUIForAuthDataResultWithResult:error:
+ @brief Shows a prompt if error is non-nil with the localized description of the error.
+ @param result The auth data result if non-nil.
+ @param error The error details to display if non-nil.
+ */
+- (void)showUIForAuthDataResultWithResult:(FIRAuthDataResult *)result
+ error:(NSError *)error {
+ NSString *errorMessage = [NSString stringWithFormat:@"%@ (%ld)\n%@",
+ error.domain ?: @"",
+ (long)error.code,
+ error.localizedDescription ?: @""];
+ [self showMessagePromptWithTitle:@"Error"
+ message:errorMessage
+ showCancelButton:NO
+ completion:^(BOOL userPressedOK,
+ NSString *_Nullable userInput) {
+ NSString *profileMessaage =
+ [NSString stringWithFormat:@"%@", result.additionalUserInfo.profile ?: @""];
+ [self showMessagePromptWithTitle:@"Profile Info"
+ message:profileMessaage
+ showCancelButton:NO
+ completion:nil];
+ [self updateUserInfo];
+ }];
+}
+
+- (void)doSignInWithCustomToken:(NSString *_Nullable)userEnteredTokenText {
+ [[FIRAuth auth] signInWithCustomToken:userEnteredTokenText
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ if (error) {
+ [self logFailure:@"sign-in with custom token failed" error:error];
+ [self showMessagePromptWithTitle:kSignInErrorAlertTitle
+ message:error.localizedDescription
+ showCancelButton:NO
+ completion:nil];
+ return;
+ }
+ [self logSuccess:@"sign-in with custom token succeeded."];
+ [self showMessagePromptWithTitle:kSignedInAlertTitle
+ message:user.displayName
+ showCancelButton:NO
+ completion:nil];
+ }];
+}
+
+- (void)updateUserInfo {
+ [_userInfoTableViewCell updateContentsWithUser:[FIRAuth auth].currentUser];
+ [_userInMemoryInfoTableViewCell updateContentsWithUser:_userInMemory];
+}
+
+- (void)authStateChangedForAuth:(NSNotification *)notification {
+ [self updateUserInfo];
+ if (notification) {
+ [self log:[NSString stringWithFormat:
+ @"received FIRAuthStateDidChange notification on user '%@'.",
+ ((FIRAuth *)notification.object).currentUser.uid]];
+ }
+}
+
+/** @fn clearConsole
+ @brief Clears the console text.
+ */
+- (IBAction)clearConsole:(id)sender {
+ [_consoleString appendString:@"\n\n"];
+ _consoleTextView.text = @"";
+}
+
+/** @fn copyConsole
+ @brief Copies the current console string to the clipboard.
+ */
+- (IBAction)copyConsole:(id)sender {
+ UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
+ pasteboard.string = _consoleString ?: @"";
+}
+
+@end
diff --git a/AuthSamples/Sample/MainViewController.xib b/AuthSamples/Sample/MainViewController.xib
new file mode 100644
index 0000000..ec4b75a
--- /dev/null
+++ b/AuthSamples/Sample/MainViewController.xib
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+ <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MainViewController">
+ <connections>
+ <outlet property="consoleTextView" destination="n2N-pY-4Y5" id="8Rx-K5-QmU"/>
+ <outlet property="tableView" destination="Hll-rc-gfA" id="0mG-y8-irt"/>
+ <outlet property="tableViewManager" destination="A6y-Y4-frp" id="2mZ-dg-wS4"/>
+ <outlet property="userInMemoryInfoTableViewCell" destination="zh0-45-TuQ" id="Bi6-s7-kLG"/>
+ <outlet property="userInfoTableViewCell" destination="bYf-Pj-jVY" id="p0a-Bx-hvv"/>
+ <outlet property="userToUseCell" destination="LsZ-4s-P51" id="X9v-it-wLD"/>
+ <outlet property="view" destination="iN0-l3-epB" id="sGX-Mq-1Uy"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view contentMode="scaleToFill" id="iN0-l3-epB">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="Hll-rc-gfA">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="417"/>
+ <color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="calibratedRGB"/>
+ <connections>
+ <outlet property="dataSource" destination="A6y-Y4-frp" id="R4X-fh-aFx"/>
+ <outlet property="delegate" destination="A6y-Y4-frp" id="jHu-Pv-9wv"/>
+ </connections>
+ </tableView>
+ <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="n2N-pY-4Y5">
+ <rect key="frame" x="0.0" y="425" width="600" height="175"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="175" id="s61-xh-P20"/>
+ </constraints>
+ <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+ </textView>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hEI-H1-vZs">
+ <rect key="frame" x="570" y="425" width="30" height="30"/>
+ <state key="normal" title="CL"/>
+ <connections>
+ <action selector="clearConsole:" destination="-1" eventType="touchUpInside" id="Hn2-Sp-p8a"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ONk-oy-W6d">
+ <rect key="frame" x="570" y="449" width="30" height="30"/>
+ <state key="normal" title="CP"/>
+ <connections>
+ <action selector="copyConsole:" destination="-1" eventType="touchUpInside" id="r9j-zp-kN7"/>
+ </connections>
+ </button>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="ONk-oy-W6d" secondAttribute="trailing" id="22R-Wv-nx7"/>
+ <constraint firstAttribute="trailing" secondItem="Hll-rc-gfA" secondAttribute="trailing" id="GWn-K4-V3D"/>
+ <constraint firstAttribute="bottom" secondItem="Hll-rc-gfA" secondAttribute="bottom" constant="183" id="K99-aB-Iqr"/>
+ <constraint firstItem="Hll-rc-gfA" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="UAz-Ca-RCH"/>
+ <constraint firstAttribute="bottom" secondItem="n2N-pY-4Y5" secondAttribute="bottom" id="ZVy-cZ-eIb"/>
+ <constraint firstItem="n2N-pY-4Y5" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="aaF-Ps-Qpu"/>
+ <constraint firstAttribute="bottom" secondItem="ONk-oy-W6d" secondAttribute="bottom" constant="121" id="bcX-TK-lMF"/>
+ <constraint firstAttribute="trailing" secondItem="n2N-pY-4Y5" secondAttribute="trailing" id="jsn-wx-OIr"/>
+ <constraint firstAttribute="trailing" secondItem="hEI-H1-vZs" secondAttribute="trailing" id="mpz-9q-ALh"/>
+ <constraint firstAttribute="bottom" secondItem="hEI-H1-vZs" secondAttribute="bottom" constant="145" id="otK-c8-X66"/>
+ <constraint firstItem="Hll-rc-gfA" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="yT4-GR-MIH"/>
+ </constraints>
+ <point key="canvasLocation" x="448" y="402"/>
+ </view>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="107" id="bYf-Pj-jVY" customClass="UserTableViewCell">
+ <rect key="frame" x="0.0" y="0.0" width="500" height="107"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="bYf-Pj-jVY" id="Ulk-yV-azP">
+ <rect key="frame" x="0.0" y="0.0" width="500" height="106.5"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="hPP-eY-9fW">
+ <rect key="frame" x="8" y="8" width="90" height="90"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="90" id="5y3-rD-gXk"/>
+ <constraint firstAttribute="width" secondItem="hPP-eY-9fW" secondAttribute="height" multiplier="1:1" id="AqJ-2w-dcT"/>
+ <constraint firstAttribute="width" secondItem="hPP-eY-9fW" secondAttribute="height" multiplier="1:1" id="z7U-Am-95c"/>
+ </constraints>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="z7U-Am-95c"/>
+ </mask>
+ </variation>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p6v-63-AuV">
+ <rect key="frame" x="106" y="8" width="314" height="21"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EfM-PI-Xnj">
+ <rect key="frame" x="106" y="32" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="314" id="KEA-3Z-pMi"/>
+ <constraint firstAttribute="height" constant="21" id="jOF-DJ-Sml"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="KEA-3Z-pMi"/>
+ </mask>
+ </variation>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Provider List" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="my0-p6-pH6">
+ <rect key="frame" x="106" y="78" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="21" id="62Z-fc-RHm"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iaQ-zf-G2U">
+ <rect key="frame" x="428" y="8" width="64" height="90"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="64" id="R5X-0j-Lao"/>
+ </constraints>
+ <state key="normal" title="M+"/>
+ <connections>
+ <action selector="memoryPlus" destination="-1" eventType="touchUpInside" id="gvb-Km-J8l"/>
+ </connections>
+ </button>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="h1y-Rb-peJ">
+ <rect key="frame" x="106" y="55" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="21" id="780-NW-d3a"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <constraints>
+ <constraint firstItem="hPP-eY-9fW" firstAttribute="top" secondItem="Ulk-yV-azP" secondAttribute="topMargin" id="0ry-HJ-3vx"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="leading" secondItem="hPP-eY-9fW" secondAttribute="trailing" constant="8" symbolic="YES" id="564-bQ-COy"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="bottom" secondItem="hPP-eY-9fW" secondAttribute="bottom" id="9hR-kP-6Tf"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="leading" secondItem="h1y-Rb-peJ" secondAttribute="trailing" constant="8" symbolic="YES" id="BmY-H5-2NO"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="trailing" secondItem="Ulk-yV-azP" secondAttribute="trailingMargin" id="CeI-mb-oeM"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="leading" secondItem="my0-p6-pH6" secondAttribute="leading" id="D91-aG-k83"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="top" secondItem="Ulk-yV-azP" secondAttribute="top" constant="32" id="DLn-9X-y8c"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="bottom" secondItem="hPP-eY-9fW" secondAttribute="bottom" id="EAV-7c-fG4"/>
+ <constraint firstAttribute="bottomMargin" secondItem="my0-p6-pH6" secondAttribute="bottom" constant="-0.5" id="LTJ-U8-4RR"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="leading" secondItem="EfM-PI-Xnj" secondAttribute="trailing" constant="8" symbolic="YES" id="NnB-3j-eyJ"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="leading" secondItem="my0-p6-pH6" secondAttribute="trailing" constant="8" symbolic="YES" id="OVL-5A-xCf"/>
+ <constraint firstItem="hPP-eY-9fW" firstAttribute="leading" secondItem="Ulk-yV-azP" secondAttribute="leadingMargin" id="P1h-Uy-1Ac"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="leading" secondItem="p6v-63-AuV" secondAttribute="trailing" constant="8" symbolic="YES" id="WaP-nw-Pa4"/>
+ <constraint firstItem="my0-p6-pH6" firstAttribute="top" secondItem="h1y-Rb-peJ" secondAttribute="bottom" constant="2" id="YWo-9n-6NP"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="leading" secondItem="p6v-63-AuV" secondAttribute="leading" id="adW-uj-LKW"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="top" secondItem="p6v-63-AuV" secondAttribute="top" id="c9T-vM-kCC"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="trailing" secondItem="Ulk-yV-azP" secondAttribute="trailingMargin" id="cru-24-h18"/>
+ <constraint firstItem="iaQ-zf-G2U" firstAttribute="top" secondItem="p6v-63-AuV" secondAttribute="top" id="eEr-FK-szm"/>
+ <constraint firstItem="h1y-Rb-peJ" firstAttribute="top" secondItem="EfM-PI-Xnj" secondAttribute="bottom" constant="2" id="iYn-Tj-cB5"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="top" secondItem="p6v-63-AuV" secondAttribute="bottom" constant="3" id="qhp-Cy-lfn"/>
+ <constraint firstItem="hPP-eY-9fW" firstAttribute="bottom" secondItem="Ulk-yV-azP" secondAttribute="bottomMargin" id="stR-OV-FUd"/>
+ <constraint firstItem="EfM-PI-Xnj" firstAttribute="leading" secondItem="h1y-Rb-peJ" secondAttribute="leading" id="tQp-sn-zMN"/>
+ </constraints>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="DLn-9X-y8c"/>
+ <exclude reference="EAV-7c-fG4"/>
+ <exclude reference="cru-24-h18"/>
+ <exclude reference="eEr-FK-szm"/>
+ </mask>
+ </variation>
+ </tableViewCellContentView>
+ <connections>
+ <outlet property="userInfoDisplayNameLabel" destination="p6v-63-AuV" id="jrN-fo-dpo"/>
+ <outlet property="userInfoEmailLabel" destination="EfM-PI-Xnj" id="lup-fo-bWg"/>
+ <outlet property="userInfoProfileURLImageView" destination="hPP-eY-9fW" id="YAH-VZ-TUG"/>
+ <outlet property="userInfoProviderListLabel" destination="my0-p6-pH6" id="MSK-wX-Nst"/>
+ <outlet property="userInfoUserIDLabel" destination="h1y-Rb-peJ" id="3bJ-bB-ofq"/>
+ </connections>
+ <point key="canvasLocation" x="-191" y="325.5"/>
+ </tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="107" id="zh0-45-TuQ" customClass="UserTableViewCell">
+ <rect key="frame" x="0.0" y="0.0" width="500" height="107"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zh0-45-TuQ" id="XNx-rO-smo">
+ <rect key="frame" x="0.0" y="0.0" width="500" height="106.5"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="hLa-iS-QtA">
+ <rect key="frame" x="8" y="8" width="90" height="90"/>
+ <constraints>
+ <constraint firstAttribute="width" secondItem="hLa-iS-QtA" secondAttribute="height" multiplier="1:1" id="3WU-f2-f8i"/>
+ <constraint firstAttribute="width" constant="90" id="dGC-3Y-Lqd"/>
+ <constraint firstAttribute="width" secondItem="hLa-iS-QtA" secondAttribute="height" multiplier="1:1" id="kAm-w5-Ron"/>
+ </constraints>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="3WU-f2-f8i"/>
+ </mask>
+ </variation>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ycp-Vc-BY7">
+ <rect key="frame" x="106" y="8" width="314" height="21"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5WQ-KD-Jcl">
+ <rect key="frame" x="106" y="32" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="21" id="umR-qC-7qF"/>
+ <constraint firstAttribute="width" constant="314" id="vXg-uI-D8c"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="vXg-uI-D8c"/>
+ </mask>
+ </variation>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Provider List" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6FV-lo-RuN">
+ <rect key="frame" x="106" y="78" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="21" id="RVn-Wa-Xbp"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bEJ-Be-y7a">
+ <rect key="frame" x="428" y="8" width="64" height="90"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="64" id="yRu-DH-Oet"/>
+ </constraints>
+ <state key="normal" title="MC"/>
+ <connections>
+ <action selector="memoryClear" destination="-1" eventType="touchUpInside" id="joZ-N8-BDY"/>
+ </connections>
+ </button>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FQp-Lq-EBp">
+ <rect key="frame" x="106" y="55" width="314" height="21"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="21" id="tsz-9R-eJf"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="bottomMargin" secondItem="6FV-lo-RuN" secondAttribute="bottom" constant="-0.5" id="1Hv-LI-PbT"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="leading" secondItem="5WQ-KD-Jcl" secondAttribute="trailing" constant="8" symbolic="YES" id="7j5-BA-KFD"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="trailing" secondItem="XNx-rO-smo" secondAttribute="trailingMargin" id="G45-sQ-DYR"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="leading" secondItem="ycp-Vc-BY7" secondAttribute="trailing" constant="8" symbolic="YES" id="OL1-KX-AJU"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="leading" secondItem="6FV-lo-RuN" secondAttribute="leading" id="Q3L-KT-htb"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="leading" secondItem="6FV-lo-RuN" secondAttribute="trailing" constant="8" symbolic="YES" id="RD5-7i-oWq"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="leading" secondItem="ycp-Vc-BY7" secondAttribute="leading" id="RQv-20-b4H"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="top" secondItem="ycp-Vc-BY7" secondAttribute="top" id="U5d-lG-KWY"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="leading" secondItem="hLa-iS-QtA" secondAttribute="trailing" constant="8" symbolic="YES" id="Xme-JH-VMQ"/>
+ <constraint firstItem="hLa-iS-QtA" firstAttribute="bottom" secondItem="XNx-rO-smo" secondAttribute="bottomMargin" id="Zlj-2B-bQb"/>
+ <constraint firstItem="hLa-iS-QtA" firstAttribute="leading" secondItem="XNx-rO-smo" secondAttribute="leadingMargin" id="a7r-k3-LXl"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="bottom" secondItem="hLa-iS-QtA" secondAttribute="bottom" id="dlc-Um-0f3"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="leading" secondItem="FQp-Lq-EBp" secondAttribute="leading" id="ewD-ct-OhC"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="top" secondItem="ycp-Vc-BY7" secondAttribute="top" id="f9v-kk-otf"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="leading" secondItem="FQp-Lq-EBp" secondAttribute="trailing" constant="8" symbolic="YES" id="fNb-Xp-gKX"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="top" secondItem="ycp-Vc-BY7" secondAttribute="bottom" constant="3" id="gkP-oU-TGY"/>
+ <constraint firstItem="hLa-iS-QtA" firstAttribute="top" secondItem="XNx-rO-smo" secondAttribute="topMargin" id="p02-MJ-1ff"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="bottom" secondItem="hLa-iS-QtA" secondAttribute="bottom" id="qFD-Ju-CRU"/>
+ <constraint firstItem="5WQ-KD-Jcl" firstAttribute="top" secondItem="XNx-rO-smo" secondAttribute="top" constant="32" id="qmQ-z2-aTk"/>
+ <constraint firstItem="6FV-lo-RuN" firstAttribute="top" secondItem="FQp-Lq-EBp" secondAttribute="bottom" constant="2" id="vEE-lZ-IV7"/>
+ <constraint firstItem="FQp-Lq-EBp" firstAttribute="top" secondItem="5WQ-KD-Jcl" secondAttribute="bottom" constant="2" id="wG2-Da-pns"/>
+ <constraint firstItem="bEJ-Be-y7a" firstAttribute="trailing" secondItem="XNx-rO-smo" secondAttribute="trailingMargin" id="yt3-Zp-StK"/>
+ </constraints>
+ <variation key="default">
+ <mask key="constraints">
+ <exclude reference="qmQ-z2-aTk"/>
+ <exclude reference="dlc-Um-0f3"/>
+ <exclude reference="yt3-Zp-StK"/>
+ <exclude reference="f9v-kk-otf"/>
+ </mask>
+ </variation>
+ </tableViewCellContentView>
+ <connections>
+ <outlet property="userInfoDisplayNameLabel" destination="ycp-Vc-BY7" id="8Og-Xh-auj"/>
+ <outlet property="userInfoEmailLabel" destination="5WQ-KD-Jcl" id="wdv-q0-g1A"/>
+ <outlet property="userInfoProfileURLImageView" destination="hLa-iS-QtA" id="3X8-jL-d24"/>
+ <outlet property="userInfoProviderListLabel" destination="6FV-lo-RuN" id="FbB-Op-xQz"/>
+ <outlet property="userInfoUserIDLabel" destination="FQp-Lq-EBp" id="SlI-ZP-yuo"/>
+ </connections>
+ <point key="canvasLocation" x="-191" y="574.5"/>
+ </tableViewCell>
+ <customObject id="A6y-Y4-frp" customClass="StaticContentTableViewManager">
+ <connections>
+ <outlet property="tableView" destination="Hll-rc-gfA" id="Lk4-jl-dP0"/>
+ </connections>
+ </customObject>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="LsZ-4s-P51">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="LsZ-4s-P51" id="G9s-Xs-aPm">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="Vev-VJ-WgW">
+ <rect key="frame" x="50" y="8" width="221" height="29"/>
+ <segments>
+ <segment title="Signed In User"/>
+ <segment title="User in Memory"/>
+ </segments>
+ <connections>
+ <action selector="userToUseDidChange:" destination="-1" eventType="valueChanged" id="gPh-ZP-M2b"/>
+ </connections>
+ </segmentedControl>
+ </subviews>
+ <constraints>
+ <constraint firstItem="Vev-VJ-WgW" firstAttribute="centerX" secondItem="G9s-Xs-aPm" secondAttribute="centerX" id="E6z-Xc-Mjr"/>
+ <constraint firstItem="Vev-VJ-WgW" firstAttribute="centerY" secondItem="G9s-Xs-aPm" secondAttribute="centerY" id="ijR-6Y-K29"/>
+ </constraints>
+ </tableViewCellContentView>
+ <point key="canvasLocation" x="-191" y="449"/>
+ </tableViewCell>
+ </objects>
+</document>
diff --git a/AuthSamples/Sample/Sample.entitlements.dev b/AuthSamples/Sample/Sample.entitlements.dev
new file mode 100644
index 0000000..9199dae
--- /dev/null
+++ b/AuthSamples/Sample/Sample.entitlements.dev
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>application-identifier</key>
+ <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+ <key>aps-environment</key>
+ <string>development</string>
+</dict>
+</plist>
diff --git a/AuthSamples/Sample/Sample.entitlements.enterprise b/AuthSamples/Sample/Sample.entitlements.enterprise
new file mode 100644
index 0000000..edc26b2
--- /dev/null
+++ b/AuthSamples/Sample/Sample.entitlements.enterprise
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>application-identifier</key>
+ <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+ <key>aps-environment</key>
+ <string>production</string>
+</dict>
+</plist>
diff --git a/AuthSamples/Sample/SettingsViewController.h b/AuthSamples/Sample/SettingsViewController.h
new file mode 100644
index 0000000..be7b752
--- /dev/null
+++ b/AuthSamples/Sample/SettingsViewController.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+@class StaticContentTableViewManager;
+
+/** @class SettingsViewController
+ @brief A view controller for sample app info and settings.
+ */
+@interface SettingsViewController : UIViewController
+
+/** @property tableViewManager
+ @brief A @c StaticContentTableViewManager which is used to manage the contents of the table
+ view.
+ */
+@property(nonatomic, strong) IBOutlet StaticContentTableViewManager *tableViewManager;
+
+/** @fn done
+ @brief Called when user taps the "Done" button.
+ */
+- (IBAction)done:(id)sender;
+
+@end
diff --git a/AuthSamples/Sample/SettingsViewController.m b/AuthSamples/Sample/SettingsViewController.m
new file mode 100644
index 0000000..e7be9a6
--- /dev/null
+++ b/AuthSamples/Sample/SettingsViewController.m
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "SettingsViewController.h"
+
+#import <objc/runtime.h>
+
+#import "FIRApp.h"
+#import "FIROptions.h"
+#import "FirebaseAuth.h"
+#import "StaticContentTableViewManager.h"
+#import "UIViewController+Alerts.h"
+
+#if INTERNAL_GOOGLE3_BUILD
+#import "googlemac/iPhone/Identity/Firebear/Auth/Source/FIRAuth_Internal.h"
+#import "googlemac/iPhone/Identity/Firebear/Auth/Source/FIRAuthAPNSToken.h"
+#import "googlemac/iPhone/Identity/Firebear/Auth/Source/FIRAuthAPNSTokenManager.h"
+#import "googlemac/iPhone/Identity/Firebear/Auth/Source/FIRAuthAppCredential.h"
+#import "googlemac/iPhone/Identity/Firebear/Auth/Source/FIRAuthAppCredentialManager.h"
+#import "googlemac/iPhone/InstanceID/Firebase/Lib/Source/FIRInstanceID+Internal.h"
+#else
+@interface FIRInstanceID : NSObject
++ (void)notifyTokenRefresh;
+@end
+#endif
+
+/** @var kIdentityToolkitRequestClassName
+ @brief The class name of Identity Toolkit requests.
+ */
+static NSString *const kIdentityToolkitRequestClassName = @"FIRIdentityToolkitRequest";
+
+/** @var kSecureTokenRequestClassName
+ @brief The class name of Secure Token Service requests.
+ */
+static NSString *const kSecureTokenRequestClassName = @"FIRSecureTokenRequest";
+
+/** @var kIdentityToolkitSandboxHost
+ @brief The host of Identity Toolkit sandbox server.
+ */
+static NSString *const kIdentityToolkitSandboxHost = @"www-googleapis-staging.sandbox.google.com";
+
+/** @var kSecureTokenSandboxHost
+ @brief The host of Secure Token Service sandbox server.
+ */
+static NSString *const kSecureTokenSandboxHost = @"staging-securetoken.sandbox.googleapis.com";
+
+/** @var kGoogleServiceInfoPlists
+ @brief a C-array of plist file base names of Google service info to initialize FIRApp.
+ */
+static NSString *const kGoogleServiceInfoPlists[] = {
+ @"GoogleService-Info",
+ @"GoogleService-Info_multi"
+};
+
+/** @var gAPIEndpoints
+ @brief List of API Hosts by request class name.
+ */
+static NSDictionary<NSString *, NSArray<NSString *> *> *gAPIHosts;
+
+/** @var gFirebaseAppOptions
+ @brief List of FIROptions.
+ */
+static NSArray<FIROptions *> *gFirebaseAppOptions;
+
+/** @protocol RequestClass
+ @brief A de-facto protocol followed by request class objects to access its API host.
+ */
+@protocol RequestClass <NSObject>
+- (NSString *)host;
+- (void)setHost:(NSString *)host;
+@end
+
+/** @category FIROptions(ProjectID)
+ @brief A category to FIROption to add the project ID property.
+ */
+@interface FIROptions (ProjectID)
+
+/** @property projectID
+ @brief The Firebase project ID.
+ */
+@property(nonatomic, copy) NSString *projectID;
+
+@end
+
+@implementation FIROptions (ProjectID)
+
+- (NSString *)projectID {
+ return objc_getAssociatedObject(self, @selector(projectID));
+}
+
+- (void)setProjectID:(NSString *)projectID {
+ objc_setAssociatedObject(self, @selector(projectID), projectID, OBJC_ASSOCIATION_COPY_NONATOMIC);
+}
+
+@end
+
+/** @fn versionString
+ @brief Constructs a version string to display.
+ @param string The version in string form.
+ @param number The version in number form.
+ */
+static NSString *versionString(const unsigned char *string, const double number) {
+ return [NSString stringWithFormat:@"\"%s\" (%g)", string, number];
+}
+
+/** @fn requestHost
+ @brief Retrieves the API host for the request class.
+ @param requestClassName The name of the request class.
+ */
+static NSString *APIHost(NSString *requestClassName) {
+ return [(id<RequestClass>)NSClassFromString(requestClassName) host];
+}
+
+/** @fn truncatedString
+ @brief Truncates a string under a maximum length.
+ @param string The original string to be truncated.
+ @param length The maximum length of the truncated string.
+ @return The truncated string, which is not longer than @c length.
+ */
+static NSString *truncatedString(NSString *string, NSUInteger length) {
+ if (string.length <= length) {
+ return string;
+ }
+ NSUInteger half = (length - 3) / 2;
+ return [NSString stringWithFormat:@"%@...%@",
+ [string substringToIndex:half],
+ [string substringFromIndex:string.length - half]];
+}
+
+/** @fn hexString
+ @brief Converts a piece of data into a hexadecimal string.
+ @param data The raw data.
+ @return The hexadecimal string representation of the data.
+ */
+static NSString *hexString(NSData *data) {
+ NSMutableString *string = [NSMutableString stringWithCapacity:data.length * 2];
+ const unsigned char *bytes = data.bytes;
+ for (int idx = 0; idx < data.length; ++idx) {
+ [string appendFormat:@"%02X", (int)bytes[idx]];
+ }
+ return string;
+}
+
+@implementation SettingsViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self setUpAPIHosts];
+ [self setUpFirebaseAppOptions];
+ [self loadTableView];
+}
+
+- (IBAction)done:(id)sender {
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)setUpAPIHosts {
+ if (gAPIHosts) {
+ return;
+ }
+ gAPIHosts = @{
+ kIdentityToolkitRequestClassName : @[
+ APIHost(kIdentityToolkitRequestClassName),
+ kIdentityToolkitSandboxHost,
+ ],
+ kSecureTokenRequestClassName : @[
+ APIHost(kSecureTokenRequestClassName),
+ kSecureTokenSandboxHost,
+ ],
+ };
+}
+
+- (void)setUpFirebaseAppOptions {
+ if (gFirebaseAppOptions) {
+ return;
+ }
+ int numberOfOptions = sizeof(kGoogleServiceInfoPlists) / sizeof(*kGoogleServiceInfoPlists);
+ NSMutableArray *appOptions = [[NSMutableArray alloc] initWithCapacity:numberOfOptions];
+ for (int i = 0; i < numberOfOptions; i++) {
+ NSString *plistFileName = kGoogleServiceInfoPlists[i];
+ NSString *plistFilePath = [[NSBundle mainBundle] pathForResource:plistFileName
+ ofType:@"plist"];
+ NSDictionary *optionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
+ FIROptions *options = [[FIROptions alloc]
+ initWithGoogleAppID:optionsDictionary[@"GOOGLE_APP_ID"]
+ bundleID:optionsDictionary[@"BUNDLE_ID"]
+ GCMSenderID:optionsDictionary[@"GCM_SENDER_ID"]
+ APIKey:optionsDictionary[@"API_KEY"]
+ clientID:optionsDictionary[@"CLIENT_ID"]
+ trackingID:optionsDictionary[@"TRACKING_ID"]
+ androidClientID:optionsDictionary[@"ANDROID_CLIENT_ID"]
+ databaseURL:optionsDictionary[@"DATABASE_URL"]
+ storageBucket:optionsDictionary[@"STORAGE_BUCKET"]
+ deepLinkURLScheme:nil];
+ options.projectID = optionsDictionary[@"PROJECT_ID"];
+ [appOptions addObject:options];
+ }
+ gFirebaseAppOptions = [appOptions copy];
+}
+
+- (void)loadTableView {
+ __weak typeof(self) weakSelf = self;
+ _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[
+ [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"FirebaseAuth"
+ value:versionString(
+ FirebaseAuthVersionString, FirebaseAuthVersionNumber)],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:@"Client Identity" cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"Project"
+ value:[self currentProjectID]
+ action:^{
+ [weakSelf toggleClientProject];
+ }],
+ ]],
+ [StaticContentTableViewSection sectionWithTitle:@"API Hosts" cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"Identity Toolkit"
+ value:APIHost(kIdentityToolkitRequestClassName)
+ action:^{
+ [weakSelf toggleAPIHostWithRequestClassName:kIdentityToolkitRequestClassName];
+ }],
+ [StaticContentTableViewCell cellWithTitle:@"Secure Token"
+ value:APIHost(kSecureTokenRequestClassName)
+ action:^{
+ [weakSelf toggleAPIHostWithRequestClassName:kSecureTokenRequestClassName];
+ }],
+ ]],
+#if INTERNAL_GOOGLE3_BUILD
+ [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"APNs Token"
+ value:[self APNSTokenString]
+ action:^{
+ [weakSelf clearAPNSToken];
+ }],
+ [StaticContentTableViewCell cellWithTitle:@"App Credential"
+ value:[self appCredentialString]
+ action:^{
+ [weakSelf clearAppCredential];
+ }],
+ ]],
+#endif // INTERNAL_GOOGLE3_BUILD
+ ]];
+}
+
+/** @fn toggleAPIHostWithRequestClassName:
+ @brief Toggles the host name of the server that handles RPCs.
+ @param requestClassName The name of the RPC request class.
+ */
+- (void)toggleAPIHostWithRequestClassName:(NSString *)requestClassName {
+ NSString *currentHost = APIHost(requestClassName);
+ NSArray<NSString *> *allHosts = gAPIHosts[requestClassName];
+ NSString *newHost = allHosts[([allHosts indexOfObject:currentHost] + 1) % allHosts.count];
+ [(id<RequestClass>)NSClassFromString(requestClassName) setHost:newHost];
+ [self loadTableView];
+}
+
+/** @fn currentProjectID
+ @brief Returns the the current Firebase project ID.
+ */
+- (NSString *)currentProjectID {
+ NSString *APIKey = [FIRApp defaultApp].options.APIKey;
+ for (FIROptions *options in gFirebaseAppOptions) {
+ if ([options.APIKey isEqualToString:APIKey]) {
+ return options.projectID;
+ }
+ }
+ return nil;
+}
+
+/** @fn toggleClientProject
+ @brief Toggles the Firebase/Google project this client presents by recreating the default
+ FIRApp instance with different options.
+ */
+- (void)toggleClientProject {
+ NSString *APIKey = [FIRApp defaultApp].options.APIKey;
+ for (NSUInteger i = 0 ; i < gFirebaseAppOptions.count; i++) {
+ FIROptions *options = gFirebaseAppOptions[i];
+ if ([options.APIKey isEqualToString:APIKey]) {
+ __weak typeof(self) weakSelf = self;
+ [[FIRApp defaultApp] deleteApp:^(BOOL success) {
+ if (success) {
+ [FIRInstanceID notifyTokenRefresh]; // b/28967043
+ dispatch_async(dispatch_get_main_queue(), ^() {
+ FIROptions *options = gFirebaseAppOptions[(i + 1) % gFirebaseAppOptions.count];
+ [FIRApp configureWithOptions:options];
+ [weakSelf loadTableView];
+ });
+ }
+ }];
+ return;
+ }
+ }
+}
+
+#if INTERNAL_GOOGLE3_BUILD
+
+/** @fn APNSTokenString
+ @brief Returns a string representing APNS token.
+ */
+- (NSString *)APNSTokenString {
+ FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
+ if (!token) {
+ return @"";
+ }
+ return [NSString stringWithFormat:@"%@(%@)",
+ truncatedString(hexString(token.data), 19),
+ token.type == FIRAuthAPNSTokenTypeProd ? @"P" : @"S"];
+}
+
+/** @fn clearAPNSToken
+ @brief Clears the saved app credential.
+ */
+- (void)clearAPNSToken {
+ FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
+ if (!token) {
+ return;
+ }
+ NSString *tokenType = token.type == FIRAuthAPNSTokenTypeProd ? @"Production" : @"Sandbox";
+ NSString *message = [NSString stringWithFormat:@"token: %@\ntype: %@",
+ hexString(token.data), tokenType];
+ [self showMessagePromptWithTitle:@"Clear APNs Token?"
+ message:message
+ showCancelButton:YES
+ completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (userPressedOK) {
+ [FIRAuth auth].tokenManager.token = nil;
+ [self loadTableView];
+ }
+ }];
+}
+
+/** @fn appCredentialString
+ @brief Returns a string representing app credential.
+ */
+- (NSString *)appCredentialString {
+ FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
+ if (!credential) {
+ return @"";
+ }
+ return [NSString stringWithFormat:@"%@/%@",
+ truncatedString(credential.receipt, 13),
+ truncatedString(credential.secret, 13)];
+}
+
+/** @fn clearAppCredential
+ @brief Clears the saved app credential.
+ */
+- (void)clearAppCredential {
+ FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
+ if (!credential) {
+ return;
+ }
+ NSString *message = [NSString stringWithFormat:@"receipt: %@\nsecret: %@",
+ credential.receipt, credential.secret];
+ [self showMessagePromptWithTitle:@"Clear App Credential?"
+ message:message
+ showCancelButton:YES
+ completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
+ if (userPressedOK) {
+ [[FIRAuth auth].appCredentialManager clearCredential];
+ [self loadTableView];
+ }
+ }];
+}
+
+#endif // INTERNAL_GOOGLE3_BUILD
+
+@end
diff --git a/AuthSamples/Sample/SettingsViewController.xib b/AuthSamples/Sample/SettingsViewController.xib
new file mode 100644
index 0000000..4540047
--- /dev/null
+++ b/AuthSamples/Sample/SettingsViewController.xib
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SettingsViewController">
+ <connections>
+ <outlet property="tableViewManager" destination="U72-zk-rRG" id="KxN-ZQ-cAa"/>
+ <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rRb-jS-QTO">
+ <rect key="frame" x="8" y="16" width="46" height="30"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="30" id="6TV-T6-63H"/>
+ <constraint firstAttribute="width" constant="46" id="kVF-SE-Qzn"/>
+ </constraints>
+ <state key="normal" title="Done"/>
+ <connections>
+ <action selector="done:" destination="-1" eventType="touchUpInside" id="O91-6u-80J"/>
+ </connections>
+ </button>
+ <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="4BU-Kb-3Zx">
+ <rect key="frame" x="0.0" y="54" width="600" height="546"/>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <connections>
+ <outlet property="dataSource" destination="U72-zk-rRG" id="GFT-r0-uBK"/>
+ <outlet property="delegate" destination="U72-zk-rRG" id="uzl-Rm-2qd"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="rRb-jS-QTO" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="8" id="1PF-3b-y4w"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="5Tz-J0-2Dq"/>
+ <constraint firstItem="rRb-jS-QTO" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="16" id="K7t-0g-JBo"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="top" secondItem="rRb-jS-QTO" secondAttribute="bottom" constant="8" id="MyI-Bl-nGS"/>
+ <constraint firstAttribute="bottom" secondItem="4BU-Kb-3Zx" secondAttribute="bottom" id="QMm-Wv-q60"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="deo-wi-Nmq"/>
+ </constraints>
+ </view>
+ <customObject id="U72-zk-rRG" customClass="StaticContentTableViewManager">
+ <connections>
+ <outlet property="tableView" destination="4BU-Kb-3Zx" id="0OK-KI-QdH"/>
+ </connections>
+ </customObject>
+ </objects>
+</document>
diff --git a/AuthSamples/Sample/StaticContentTableViewManager.h b/AuthSamples/Sample/StaticContentTableViewManager.h
new file mode 100644
index 0000000..cb56391
--- /dev/null
+++ b/AuthSamples/Sample/StaticContentTableViewManager.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+#pragma mark - Forward Declarations
+
+@class StaticContentTableViewCell;
+@class StaticContentTableViewContent;
+@class StaticContentTableViewSection;
+
+#pragma mark - Block Type Definitions
+
+/** @typedef StaticContentTableViewCellAction
+ @brief The type of block invoked when a cell is tapped.
+ */
+typedef void(^StaticContentTableViewCellAction)(void);
+
+#pragma mark -
+
+/** @class StaticContentTableViewManager
+ @brief Generic class useful for populating a @c UITableView with static content.
+ @remarks Because I keep writing the same UITableView code for every internal testing app, and
+ it's getting too tedious and ugly to keep writing the same thing over and over. It makes
+ our sample apps harder to maintain with all this code sprinkled around everywhere, and
+ we end up cutting corners and making junky testing apps, and spending more time in the
+ process.
+ */
+@interface StaticContentTableViewManager : NSObject<UITableViewDelegate, UITableViewDataSource>
+
+/** @property contents
+ @brief The static contents of the @c UITableView.
+ @remarks Setting this property will reload the @c UITableView.
+ */
+@property(nonatomic, strong, nullable) StaticContentTableViewContent *contents;
+
+/** @property tableView
+ @brief A reference to the managed @c UITableView.
+ @remarks This is needed to automatically reload the table view when the @c contents are changed.
+ */
+@property(nonatomic, weak, nullable) IBOutlet UITableView *tableView;
+
+@end
+
+#pragma mark -
+
+/** @class StaticContentTableViewContent
+ @brief Represents the contents of a @c UITableView.
+ */
+@interface StaticContentTableViewContent : NSObject
+
+/** @property sections
+ @brief The sections for the @c UITableView.
+ */
+@property(nonatomic, copy, readonly, nullable) NSArray<StaticContentTableViewSection *> *sections;
+
+/** @fn contentWithSections:
+ @brief Convenience factory method for creating a new instance of
+ @c StaticContentTableViewContent.
+ @param sections The sections for the @c UITableView.
+ */
++ (nullable instancetype)contentWithSections:
+ (nullable NSArray<StaticContentTableViewSection *> *)sections;
+
+/** @fn init
+ @brief Please use initWithSections:
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/** @fn initWithSections:
+ @brief Designated initializer.
+ @param sections The sections in the @c UITableView.
+ */
+- (nullable instancetype)initWithSections:
+ (nullable NSArray<StaticContentTableViewSection *> *)sections;
+
+@end
+
+#pragma mark -
+
+/** @class StaticContentTableViewSection
+ @brief Represents a section in a @c UITableView.
+ @remarks Each section has a title (used for the section title in the @c UITableView) and an
+ array of cells.
+ */
+@interface StaticContentTableViewSection : NSObject
+
+/** @property title
+ @brief The title of the section in the @c UITableView.
+ */
+@property(nonatomic, copy, readonly, nullable) NSString *title;
+
+/** @property cells
+ @brief The cells in this section of the @c UITableView.
+ */
+@property(nonatomic, copy, readonly, nullable) NSArray<StaticContentTableViewCell *> *cells;
+
+/** @fn sectionWithTitle:cells:
+ @brief Convenience factory method for creating a new instance of
+ @c StaticContentTableViewSection.
+ @param title The title of the section in the @c UITableView.
+ @param cells The cells in this section of the @c UITableView.
+ */
++ (nullable instancetype)sectionWithTitle:(nullable NSString *)title
+ cells:(nullable NSArray<StaticContentTableViewCell *> *)cells;
+
+/** @fn init
+ @brief Please use initWithTitle:cells:
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/** @fn initWithTitle:cells:
+ @brief Designated initializer.
+ @param title The title of the section in the @c UITableView.
+ @param cells The cells in this section of the @c UITableView.
+ */
+- (nullable instancetype)initWithTitle:(nullable NSString *)title
+ cells:(nullable NSArray<StaticContentTableViewCell *> *)cells;
+
+@end
+
+#pragma mark -
+
+/** @class StaticContentTableViewCell
+ @brief Represents a cell in a @c UITableView.
+ @remarks Cells may be custom cells (in which you specify a @c UITableViewCell to use), or
+ simple single-label cells which you supply the title text for. It does not make sense to
+ specify both @c customCell and also @c title, but if a @c customCell is specified, it will
+ be used instead of the @c title.
+ */
+@interface StaticContentTableViewCell : NSObject
+
+/** @property customCell
+ @brief The custom @c UITableViewCell to use for this cell.
+ */
+@property(nonatomic, strong, readonly, nullable) UITableViewCell *customCell;
+
+/** @property title
+ @brief If no custom cell is being used, this is the text of the @c titleLabel of the
+ @c UITableViewCell.
+ */
+@property(nonatomic, copy, readonly, nullable) NSString *title;
+
+/** @property value
+ @brief If no custom cell is being used, this is the text of the @c detailTextLabel of the
+ @c UITableViewCell.
+ */
+@property(nonatomic, copy, readonly, nullable) NSString *value;
+
+/** @property accessibilityIdentifier
+ @brief The accessibility ID for the corresponding @c UITableViewCell.
+ */
+@property(nonatomic, copy, readonly, nullable) NSString *accessibilityIdentifier;
+
+/** @property action
+ @brief A block which is executed when the cell is selected.
+ @remarks Avoid retain cycles. Since these blocked are retained here, and your
+ @c UIViewController's object graph likely retains this object, you don't want these blocks
+ to retain your @c UIViewController. The easiest thing is just to create a weak reference to
+ your @c UIViewController and pass it a message as the only thing the block does.
+ */
+@property(nonatomic, copy, readonly, nullable) StaticContentTableViewCellAction action;
+
+/** @fn cellWithTitle:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param title The text of the @c titleLabel of the @c UITableViewCell.
+ */
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title;
+
+/** @fn cellWithTitle:value:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param title The text of the @c titleLabel of the @c UITableViewCell.
+ @param value The text of the @c detailTextLabel of the @c UITableViewCell.
+ */
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value;
+
+/** @fn cellWithTitle:action:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param title The text of the @c titleLabel of the @c UITableViewCell.
+ @param action A block which is executed when the cell is selected.
+ */
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ action:(nullable StaticContentTableViewCellAction)action;
+
+/** @fn cellWithTitle:value:action:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param title The text of the @c titleLabel of the @c UITableViewCell.
+ @param value The text of the @c detailTextLabel of the @c UITableViewCell.
+ @param action A block which is executed when the cell is selected.
+ */
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action;
+
+/** @fn cellWithTitle:value:action:accessibilityLabel:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param title The text of the @c titleLabel of the @c UITableViewCell.
+ @param value The text of the @c detailTextLabel of the @c UITableViewCell.
+ @param action A block which is executed when the cell is selected.
+ @param accessibilityID The accessibility ID to add to the cell.
+ */
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action
+ accessibilityID:(nullable NSString *)accessibilityID;
+
+/** @fn cellWithCustomCell:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param customCell The custom @c UITableViewCell to use for this cell.
+ */
++ (nullable instancetype)cellWithCustomCell:(nullable UITableViewCell *)customCell;
+
+/** @fn cellWithCustomCell:action:
+ @brief Convenience factory method for a new instance of @c StaticContentTableViewCell.
+ @param customCell The custom @c UITableViewCell to use for this cell.
+ @param action A block which is executed when the cell is selected.
+ */
++ (nullable instancetype)cellWithCustomCell:(nullable UITableViewCell *)customCell
+ action:(nullable StaticContentTableViewCellAction)action;
+
+/** @fn init
+ @brief Please use initWithCustomCell:title:action:
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/** @fn initWithCustomCell:title:action:
+ @brief Designated initializer.
+ @param customCell The custom @c UITableViewCell to use for this cell.
+ @param title If no custom cell is being used, this is the text of the @c titleLabel of the
+ @c UITableViewCell.
+ @param title If no custom cell is being used, this is the text of the @c detailTextLabel of the
+ @c UITableViewCell.
+ @param action A block which is executed when the cell is selected.
+ @param accessibilityID The accessibility ID to add to the cell.
+ */
+- (nullable instancetype)initWithCustomCell:(nullable UITableViewCell *)customCell
+ title:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action
+ accessibilityID:(nullable NSString *)accessibilityID
+ NS_DESIGNATED_INITIALIZER;
+
+@end
diff --git a/AuthSamples/Sample/StaticContentTableViewManager.m b/AuthSamples/Sample/StaticContentTableViewManager.m
new file mode 100644
index 0000000..7ac7eb7
--- /dev/null
+++ b/AuthSamples/Sample/StaticContentTableViewManager.m
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "StaticContentTableViewManager.h"
+
+/** @var kCellReuseIdentitfier
+ @brief The reuse identifier for default style table view cell.
+ */
+static NSString *const kCellReuseIdentitfier = @"reuseIdentifier";
+
+/** @var kCellReuseIdentitfier
+ @brief The reuse identifier for value style table view cell.
+ */
+static NSString *const kValueCellReuseIdentitfier = @"reuseValueIdentifier";
+
+#pragma mark -
+
+@implementation StaticContentTableViewManager
+
+- (void)setContents:(StaticContentTableViewContent *)contents {
+ _contents = contents;
+ [self.tableView reloadData];
+}
+
+- (void)setTableView:(UITableView *)tableView {
+ _tableView = tableView;
+ [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellReuseIdentitfier];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+ return _contents.sections.count;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return _contents.sections[section].cells.count;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+ return _contents.sections[section].title;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+ StaticContentTableViewCell *cellData = _contents.sections[indexPath.section].cells[indexPath.row];
+ if (cellData.customCell) {
+ return cellData.customCell.frame.size.height;
+ }
+ return 44;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView
+ cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ StaticContentTableViewCell *cellData = _contents.sections[indexPath.section].cells[indexPath.row];
+ UITableViewCell *cell = cellData.customCell;
+ if (cell) {
+ return cell;
+ }
+ if (cellData.value.length) {
+ cell = [tableView dequeueReusableCellWithIdentifier:kValueCellReuseIdentitfier];
+ if (!cell) {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
+ reuseIdentifier:kValueCellReuseIdentitfier];
+ cell.detailTextLabel.adjustsFontSizeToFitWidth = YES;
+ cell.detailTextLabel.minimumScaleFactor = 0.5;
+ }
+ cell.detailTextLabel.text = cellData.value;
+ } else {
+ // kCellReuseIdentitfier has already been registered.
+ cell = [tableView dequeueReusableCellWithIdentifier:kCellReuseIdentitfier
+ forIndexPath:indexPath];
+ }
+ cell.textLabel.text = cellData.title;
+ cell.accessibilityIdentifier = cellData.accessibilityIdentifier;
+ return cell;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ StaticContentTableViewCell *cellData = _contents.sections[indexPath.section].cells[indexPath.row];
+ BOOL hasAssociatedAction = cellData.action != nil;
+ if (hasAssociatedAction) {
+ cellData.action();
+ }
+ [tableView deselectRowAtIndexPath:indexPath animated:hasAssociatedAction];
+}
+
+@end
+
+#pragma mark -
+
+@implementation StaticContentTableViewContent
+
++ (nullable instancetype)contentWithSections:
+ (nullable NSArray<StaticContentTableViewSection *> *)sections {
+ return [[self alloc] initWithSections:sections];
+}
+
+- (nullable instancetype)initWithSections:
+ (nullable NSArray<StaticContentTableViewSection *> *)sections {
+ self = [super init];
+ if (self) {
+ _sections = [sections copy];
+ }
+ return self;
+}
+
+@end
+
+#pragma mark -
+
+@implementation StaticContentTableViewSection
+
++ (nullable instancetype)sectionWithTitle:(nullable NSString *)title
+ cells:(nullable NSArray<StaticContentTableViewCell *> *)cells {
+ return [[self alloc] initWithTitle:title cells:cells];
+}
+
+- (nullable instancetype)initWithTitle:(nullable NSString *)title
+ cells:(nullable NSArray<StaticContentTableViewCell *> *)cells {
+ self = [super init];
+ if (self) {
+ _title = [title copy];
+ _cells = [cells copy];
+ }
+ return self;
+}
+
+@end
+
+#pragma mark -
+
+@implementation StaticContentTableViewCell
+
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title {
+ return [[self alloc] initWithCustomCell:nil
+ title:title
+ value:nil
+ action:nil
+ accessibilityID:nil];
+}
+
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value {
+ return [[self alloc] initWithCustomCell:nil
+ title:title
+ value:value
+ action:nil
+ accessibilityID:nil];
+}
+
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ action:(nullable StaticContentTableViewCellAction)action {
+ return [[self alloc] initWithCustomCell:nil
+ title:title
+ value:nil
+ action:action
+ accessibilityID:nil];
+}
+
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action {
+ return [[self alloc] initWithCustomCell:nil
+ title:title
+ value:value
+ action:action
+ accessibilityID:nil];
+}
+
++ (nullable instancetype)cellWithTitle:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action
+ accessibilityID:(nullable NSString *)accessibilityID {
+ return [[self alloc] initWithCustomCell:nil
+ title:title
+ value:value
+ action:action
+ accessibilityID:accessibilityID];
+}
+
++ (nullable instancetype)cellWithCustomCell:(nullable UITableViewCell *)customCell {
+ return [[self alloc] initWithCustomCell:customCell
+ title:nil
+ value:nil
+ action:nil
+ accessibilityID:nil];
+}
+
++ (nullable instancetype)cellWithCustomCell:(nullable UITableViewCell *)customCell
+ action:(nullable StaticContentTableViewCellAction)action {
+ return [[self alloc] initWithCustomCell:customCell
+ title:nil
+ value:nil action:action
+ accessibilityID:nil];
+}
+
+- (nullable instancetype)initWithCustomCell:(nullable UITableViewCell *)customCell
+ title:(nullable NSString *)title
+ value:(nullable NSString *)value
+ action:(nullable StaticContentTableViewCellAction)action
+ accessibilityID:(nullable NSString *)accessibilityID {
+ self = [super init];
+ if (self) {
+ _customCell = customCell;
+ _title = [title copy];
+ _value = [value copy];
+ _action = action;
+ if (accessibilityID) {
+ _accessibilityIdentifier = [accessibilityID copy];
+ self.isAccessibilityElement = YES;
+ }
+ }
+ return self;
+}
+
+@end
diff --git a/AuthSamples/Sample/Strings/ar.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ar.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ar.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ca.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ca.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ca.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/cs.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/cs.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/cs.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/da.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/da.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/da.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/de.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/de.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/de.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/el.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/el.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/el.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/en.lproj/FirebaseAuthUI.strings b/AuthSamples/Sample/Strings/en.lproj/FirebaseAuthUI.strings
new file mode 100644
index 0000000..815456e
--- /dev/null
+++ b/AuthSamples/Sample/Strings/en.lproj/FirebaseAuthUI.strings
@@ -0,0 +1,2 @@
+/* Title for auth picker screen. */
+"AuthPickerTitle" = "Welcome, you are using a Custom Bundle";
diff --git a/AuthSamples/Sample/Strings/en_GB.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/en_GB.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/en_GB.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/es.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/es.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/es.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/es_MX.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/es_MX.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/es_MX.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/fi.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/fi.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/fi.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/fr.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/fr.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/fr.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/he.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/he.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/he.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/hr.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/hr.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/hr.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/hu.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/hu.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/hu.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/id.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/id.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/id.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/it.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/it.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/it.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ja.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ja.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ja.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ko.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ko.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ko.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ms.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ms.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ms.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/nb.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/nb.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/nb.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/nl.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/nl.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/nl.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/pl.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/pl.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/pl.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/pt.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/pt.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/pt.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/pt_BR.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/pt_BR.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/pt_BR.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/pt_PT.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/pt_PT.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/pt_PT.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ro.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ro.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ro.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/ru.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/ru.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/ru.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/sk.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/sk.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/sk.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/sv.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/sv.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/sv.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/th.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/th.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/th.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/tr.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/tr.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/tr.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/uk.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/uk.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/uk.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/vi.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/vi.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/vi.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/zh_CN.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/zh_CN.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/zh_CN.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/Strings/zh_TW.lproj/FirebearSample.strings b/AuthSamples/Sample/Strings/zh_TW.lproj/FirebearSample.strings
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/AuthSamples/Sample/Strings/zh_TW.lproj/FirebearSample.strings
@@ -0,0 +1 @@
+
diff --git a/AuthSamples/Sample/UIViewController+Alerts.h b/AuthSamples/Sample/UIViewController+Alerts.h
new file mode 100644
index 0000000..375a0ed
--- /dev/null
+++ b/AuthSamples/Sample/UIViewController+Alerts.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/*! @typedef AlertPromptCompletionBlock
+ @brief The type of callback used to report text input prompt results.
+ */
+typedef void (^AlertPromptCompletionBlock)(BOOL userPressedOK, NSString *_Nullable userInput);
+
+/*! @class Alerts
+ @brief Wrapper for @c UIAlertController and @c UIAlertView for backwards compatability with
+ iOS 6+.
+ */
+@interface UIViewController (Alerts)
+
+/*! @property useStatusBarSpinner
+ @brief Uses the status bar to indicate work is occuring instead of a modal "please wait" dialog.
+ This is generally useful for allowing user interaction while things are happening.
+ */
+@property(nonatomic, assign) BOOL useStatusBarSpinner;
+
+/*! @fn showMessagePrompt:
+ @brief Displays an alert with an 'OK' button and a message.
+ @param message The message to display.
+ @remarks The message is also copied to the pasteboard.
+ */
+- (void)showMessagePrompt:(NSString *)message;
+
+/*! @fn showMessagePromptWithTitle:message:
+ @brief Displays a titled alert with an 'OK' button and a message.
+ @param title The title of the alert if it exists.
+ @param message The message to display.
+ @param showCancelButton A flag indicating whether or not a cancel option is available.
+ @param completion The completion block to be executed after the alert is dismissed, if it
+ exists.
+ @remarks The message is also copied to the pasteboard.
+ */
+- (void)showMessagePromptWithTitle:(nullable NSString *)title
+ message:(NSString *)message
+ showCancelButton:(BOOL)showCancelButton
+ completion:(nullable AlertPromptCompletionBlock)completion;
+
+/*! @fn showTextInputPromptWithMessage:keyboardType:completionBlock:
+ @brief Shows a prompt with a text field and 'OK'/'Cancel' buttons.
+ @param message The message to display.
+ @param keyboardType The type of keyboard to display for the UITextView in the prompt.
+ @param completion A block to call when the user taps 'OK' or 'Cancel'.
+ */
+- (void)showTextInputPromptWithMessage:(NSString *)message
+ keyboardType:(UIKeyboardType)keyboardType
+ completionBlock:(AlertPromptCompletionBlock)completion;
+
+/*! @fn showTextInputPromptWithMessage:completionBlock:
+ @brief Shows a prompt with a text field and 'OK'/'Cancel' buttons.
+ @param message The message to display.
+ @param completion A block to call when the user taps 'OK' or 'Cancel'.
+ */
+- (void)showTextInputPromptWithMessage:(NSString *)message
+ completionBlock:(AlertPromptCompletionBlock)completion;
+
+/*! @fn showSpinner
+ @brief Shows the please wait spinner.
+ @param completion Called after the spinner has been hidden.
+ */
+- (void)showSpinner:(nullable void(^)(void))completion;
+
+/*! @fn hideSpinner
+ @brief Hides the please wait spinner.
+ @param completion Called after the spinner has been hidden.
+ */
+- (void)hideSpinner:(nullable void(^)(void))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AuthSamples/Sample/UIViewController+Alerts.m b/AuthSamples/Sample/UIViewController+Alerts.m
new file mode 100644
index 0000000..76ef067
--- /dev/null
+++ b/AuthSamples/Sample/UIViewController+Alerts.m
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "UIViewController+Alerts.h"
+
+#import <objc/runtime.h>
+
+/*! @var kPleaseWaitAssociatedObjectKey
+ @brief Key used to identify the "please wait" spinner associated object.
+ */
+static NSString *const kPleaseWaitAssociatedObjectKey =
+ @"_UIViewControllerAlertCategory_PleaseWaitScreenAssociatedObject";
+
+/*! @var kUseStatusBarSpinnerAssociatedObjectKey
+ @brief The address of this constant is the key used to identify the "use status bar spinner"
+ associated object.
+ */
+static const void *const kUseStatusBarSpinnerAssociatedObjectKey;
+
+/*! @var kOK
+ @brief Text for an 'OK' button.
+ */
+static NSString *const kOK = @"OK";
+
+/*! @var kCancel
+ @brief Text for an 'Cancel' button.
+ */
+static NSString *const kCancel = @"Cancel";
+
+/*! @class SimpleTextPromptDelegate
+ @brief A @c UIAlertViewDelegate which allows @c UIAlertView to be used with blocks more easily.
+ */
+@interface SimpleTextPromptDelegate : NSObject <UIAlertViewDelegate>
+
+/*! @fn init
+ @brief Please use initWithCompletionHandler.
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/*! @fn initWithCompletionHandler:
+ @brief Designated initializer.
+ @param completionHandler The block to call when the alert view is dismissed.
+ */
+- (nullable instancetype)initWithCompletionHandler:(AlertPromptCompletionBlock)completionHandler
+ NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation UIViewController (Alerts)
+
+- (void)setUseStatusBarSpinner:(BOOL)useStatusBarSpinner {
+ objc_setAssociatedObject(self,
+ &kUseStatusBarSpinnerAssociatedObjectKey,
+ useStatusBarSpinner ? @(YES) : nil,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (BOOL)useStatusBarSpinner {
+ return objc_getAssociatedObject(self, &kUseStatusBarSpinnerAssociatedObjectKey) ? YES : NO;
+}
+
+/*! @fn supportsAlertController
+ @brief Determines if the current platform supports @c UIAlertController.
+ @return YES if the current platform supports @c UIAlertController.
+ */
+- (BOOL)supportsAlertController {
+ return NSClassFromString(@"UIAlertController") != nil;
+}
+
+- (void)showMessagePrompt:(NSString *)message {
+ [self showMessagePromptWithTitle:nil message:message showCancelButton:NO completion:nil];
+}
+
+- (void)showMessagePromptWithTitle:(nullable NSString *)title
+ message:(NSString *)message
+ showCancelButton:(BOOL)showCancelButton
+ completion:(nullable AlertPromptCompletionBlock)completion {
+ if (message) {
+ [UIPasteboard generalPasteboard].string = message;
+ }
+ if ([self supportsAlertController]) {
+ UIAlertController *alert =
+ [UIAlertController alertControllerWithTitle:title
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *okAction =
+ [UIAlertAction actionWithTitle:kOK
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * _Nonnull action) {
+ if (completion) {
+ completion(YES, nil);
+ }
+ }];
+ [alert addAction:okAction];
+
+ if (showCancelButton) {
+ UIAlertAction *cancelAction =
+ [UIAlertAction actionWithTitle:kCancel
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction * _Nonnull action) {
+ completion(NO, nil);
+ }];
+ [alert addAction:cancelAction];
+ }
+ [self presentViewController:alert animated:YES completion:nil];
+ } else {
+ UIAlertView *alert =
+ [[UIAlertView alloc] initWithTitle:title
+ message:message
+ delegate:nil
+ cancelButtonTitle:nil
+ otherButtonTitles:kOK, nil];
+ [alert show];
+ }
+}
+
+- (void)showTextInputPromptWithMessage:(NSString *)message
+ completionBlock:(AlertPromptCompletionBlock)completion {
+ [self showTextInputPromptWithMessage:message
+ keyboardType:UIKeyboardTypeDefault
+ completionBlock:completion];
+}
+
+- (void)showTextInputPromptWithMessage:(NSString *)message
+ keyboardType:(UIKeyboardType)keyboardType
+ completionBlock:(nonnull AlertPromptCompletionBlock)completion {
+ if ([self supportsAlertController]) {
+ UIAlertController *prompt =
+ [UIAlertController alertControllerWithTitle:nil
+ message:message
+ preferredStyle:UIAlertControllerStyleAlert];
+ __weak UIAlertController *weakPrompt = prompt;
+ UIAlertAction *cancelAction =
+ [UIAlertAction actionWithTitle:kCancel
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction * _Nonnull action) {
+ completion(NO, nil);
+ }];
+ UIAlertAction *okAction = [UIAlertAction actionWithTitle:kOK
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * _Nonnull action) {
+ UIAlertController *strongPrompt = weakPrompt;
+ completion(YES, strongPrompt.textFields[0].text);
+ }];
+ [prompt addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) {
+ textField.keyboardType = keyboardType;
+ }];
+ [prompt addAction:cancelAction];
+ [prompt addAction:okAction];
+ [self presentViewController:prompt animated:YES completion:nil];
+ } else {
+ SimpleTextPromptDelegate *prompt =
+ [[SimpleTextPromptDelegate alloc] initWithCompletionHandler:completion];
+ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
+ message:message
+ delegate:prompt
+ cancelButtonTitle:@"Cancel"
+ otherButtonTitles:@"Ok", nil];
+ alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
+ [alertView show];
+ }
+}
+
+- (void)showSpinner:(nullable void(^)(void))completion {
+ if (self.useStatusBarSpinner) {
+ [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
+ completion();
+ return;
+ }
+ if ([self supportsAlertController]) {
+ [self showModernSpinner:completion];
+ } else {
+ [self showIOS7Spinner:completion];
+ }
+}
+
+- (void)showModernSpinner:(nullable void (^)(void))completion {
+ UIAlertController *pleaseWaitAlert =
+ objc_getAssociatedObject(self,
+ (__bridge const void *)kPleaseWaitAssociatedObjectKey);
+ if (pleaseWaitAlert) {
+ if (completion) {
+ completion();
+ }
+ return;
+ }
+ pleaseWaitAlert = [UIAlertController alertControllerWithTitle:nil
+ message:@"Please Wait...\n\n\n\n"
+ preferredStyle:UIAlertControllerStyleAlert];
+
+ UIActivityIndicatorView *spinner =
+ [[UIActivityIndicatorView alloc]
+ initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ spinner.color = [UIColor blackColor];
+ spinner.center = CGPointMake(pleaseWaitAlert.view.bounds.size.width / 2,
+ pleaseWaitAlert.view.bounds.size.height / 2);
+ spinner.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |
+ UIViewAutoresizingFlexibleTopMargin |
+ UIViewAutoresizingFlexibleLeftMargin |
+ UIViewAutoresizingFlexibleRightMargin;
+ [spinner startAnimating];
+ [pleaseWaitAlert.view addSubview:spinner];
+
+ objc_setAssociatedObject(self,
+ (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
+ pleaseWaitAlert,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ [self presentViewController:pleaseWaitAlert animated:YES completion:completion];
+}
+
+- (void)showIOS7Spinner:(nullable void (^)(void))completion {
+ UIWindow *pleaseWaitWindow =
+ objc_getAssociatedObject(self,
+ (__bridge const void *)kPleaseWaitAssociatedObjectKey);
+
+ if (pleaseWaitWindow) {
+ if (completion) {
+ completion();
+ }
+ return;
+ }
+
+ pleaseWaitWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+ pleaseWaitWindow.backgroundColor = [UIColor clearColor];
+ pleaseWaitWindow.windowLevel = UIWindowLevelStatusBar - 1;
+
+ UIView *pleaseWaitView = [[UIView alloc] initWithFrame:pleaseWaitWindow.bounds];
+ pleaseWaitView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
+ UIViewAutoresizingFlexibleHeight;
+ pleaseWaitView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
+ UIActivityIndicatorView *spinner =
+ [[UIActivityIndicatorView alloc]
+ initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
+ spinner.center = pleaseWaitView.center;
+ [pleaseWaitView addSubview:spinner];
+ [spinner startAnimating];
+
+ pleaseWaitView.layer.opacity = 0.0;
+ [self.view addSubview:pleaseWaitView];
+
+ [pleaseWaitWindow addSubview:pleaseWaitView];
+
+ [pleaseWaitWindow makeKeyAndVisible];
+
+ objc_setAssociatedObject(self,
+ (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
+ pleaseWaitWindow,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ [UIView animateWithDuration:0.5f animations:^{
+ pleaseWaitView.layer.opacity = 1.0f;
+ } completion:^(BOOL finished) {
+ if (completion) {
+ completion();
+ }
+ }];
+}
+
+- (void)hideSpinner:(nullable void(^)(void))completion {
+ if (self.useStatusBarSpinner) {
+ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
+ completion();
+ return;
+ }
+ if ([self supportsAlertController]) {
+ [self hideModernSpinner:completion];
+ } else {
+ [self hideIOS7Spinner:completion];
+ }
+}
+
+- (void)hideModernSpinner:(nullable void(^)(void))completion {
+ UIAlertController *pleaseWaitAlert =
+ objc_getAssociatedObject(self,
+ (__bridge const void *)kPleaseWaitAssociatedObjectKey);
+
+ [pleaseWaitAlert dismissViewControllerAnimated:YES completion:completion];
+
+ objc_setAssociatedObject(self,
+ (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
+ nil,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (void)hideIOS7Spinner:(nullable void(^)(void))completion {
+ UIWindow *pleaseWaitWindow =
+ objc_getAssociatedObject(self,
+ (__bridge const void *)kPleaseWaitAssociatedObjectKey);
+
+ UIView *pleaseWaitView;
+ pleaseWaitView = pleaseWaitWindow.subviews.firstObject;
+
+ [UIView animateWithDuration:0.5f animations:^{
+ pleaseWaitView.layer.opacity = 0.0f;
+ } completion:^(BOOL finished) {
+ [pleaseWaitWindow resignKeyWindow];
+ objc_setAssociatedObject(self,
+ (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
+ nil,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ if (completion) {
+ completion();
+ }
+ }];
+}
+
+@end
+
+@implementation SimpleTextPromptDelegate {
+ AlertPromptCompletionBlock _completionHandler;
+ SimpleTextPromptDelegate *_retainedSelf;
+}
+
+- (instancetype)initWithCompletionHandler:(AlertPromptCompletionBlock)completionHandler {
+ self = [super init];
+ if (self) {
+ _completionHandler = completionHandler;
+ _retainedSelf = self;
+ }
+ return self;
+}
+
+- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
+ if (buttonIndex == alertView.firstOtherButtonIndex) {
+ _completionHandler(YES, [alertView textFieldAtIndex:0].text);
+ } else {
+ _completionHandler(NO, nil);
+ }
+ _completionHandler = nil;
+ _retainedSelf = nil;
+}
+
+@end
diff --git a/AuthSamples/Sample/UserInfoViewController.h b/AuthSamples/Sample/UserInfoViewController.h
new file mode 100644
index 0000000..de19bfc
--- /dev/null
+++ b/AuthSamples/Sample/UserInfoViewController.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+@class FIRUser;
+@class StaticContentTableViewManager;
+
+/** @class UserInfoViewController
+ @brief A view controller for displaying @c FIRUser data.
+ */
+@interface UserInfoViewController : UIViewController
+
+/** @property tableViewManager
+ @brief A @c StaticContentTableViewManager which is used to manage the contents of the table
+ view.
+ */
+@property(nonatomic, strong) IBOutlet StaticContentTableViewManager *tableViewManager;
+
+/** @fn initWithUser:
+ @biref Initializes with a @c FIRUser instance.
+ @param user The user to be displayed in the view.
+ */
+- (instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER;
+
+/** @fn initWithNibName:bundle:
+ @brief Not available. Call initWithUser: instead.
+ */
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil
+ bundle:(NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
+
+/** @fn initWithCoder:
+ @brief Not available. Call initWithUser: instead.
+ */
+- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
+
+/** @fn done
+ @brief Called when user taps the "Done" button.
+ */
+- (IBAction)done:(id)sender;
+
+@end
diff --git a/AuthSamples/Sample/UserInfoViewController.m b/AuthSamples/Sample/UserInfoViewController.m
new file mode 100644
index 0000000..aa5b7fe
--- /dev/null
+++ b/AuthSamples/Sample/UserInfoViewController.m
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "UserInfoViewController.h"
+
+#import "FIRUser.h"
+#import "FIRUserInfo.h"
+#import "StaticContentTableViewManager.h"
+
+/** @fn stringWithBool
+ @brief Converts a boolean value to a string for display.
+ @param boolValue the boolean value.
+ @return The string form of the boolean value.
+ */
+static NSString *stringWithBool(BOOL boolValue) {
+ return boolValue ? @"YES" : @"NO";
+}
+
+@implementation UserInfoViewController {
+ FIRUser *_user;
+}
+
+- (instancetype)initWithUser:(FIRUser *)user {
+ self = [super initWithNibName:NSStringFromClass([self class]) bundle:nil];
+ if (self) {
+ _user = user;
+ }
+ return self;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self loadTableView];
+}
+
+- (void)loadTableView {
+ NSMutableArray<StaticContentTableViewSection *> *sections = [@[
+ [StaticContentTableViewSection sectionWithTitle:@"User" cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"anonymous" value:stringWithBool(_user.anonymous)],
+ [StaticContentTableViewCell cellWithTitle:@"emailVerified"
+ value:stringWithBool(_user.emailVerified)],
+ [StaticContentTableViewCell cellWithTitle:@"refreshToken" value:_user.refreshToken],
+ ]]
+ ] mutableCopy];
+ [sections addObject:[self sectionWithUserInfo:_user]];
+ for (id<FIRUserInfo> userInfo in _user.providerData) {
+ [sections addObject:[self sectionWithUserInfo:userInfo]];
+ }
+ _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:sections];
+}
+
+- (StaticContentTableViewSection *)sectionWithUserInfo:(id<FIRUserInfo>)userInfo {
+ return [StaticContentTableViewSection sectionWithTitle:userInfo.providerID cells:@[
+ [StaticContentTableViewCell cellWithTitle:@"uid" value:userInfo.uid],
+ [StaticContentTableViewCell cellWithTitle:@"displayName" value:userInfo.displayName],
+ [StaticContentTableViewCell cellWithTitle:@"photoURL" value:[userInfo.photoURL absoluteString]],
+ [StaticContentTableViewCell cellWithTitle:@"email" value:userInfo.email],
+ [StaticContentTableViewCell cellWithTitle:@"phoneNumber" value:userInfo.phoneNumber]
+ ]];
+}
+
+- (IBAction)done:(id)sender {
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+@end
diff --git a/AuthSamples/Sample/UserInfoViewController.xib b/AuthSamples/Sample/UserInfoViewController.xib
new file mode 100644
index 0000000..9f2db98
--- /dev/null
+++ b/AuthSamples/Sample/UserInfoViewController.xib
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UserInfoViewController">
+ <connections>
+ <outlet property="tableViewManager" destination="U72-zk-rRG" id="KxN-ZQ-cAa"/>
+ <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rRb-jS-QTO">
+ <rect key="frame" x="8" y="16" width="46" height="30"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="30" id="6TV-T6-63H"/>
+ <constraint firstAttribute="width" constant="46" id="kVF-SE-Qzn"/>
+ </constraints>
+ <state key="normal" title="Done"/>
+ <connections>
+ <action selector="done:" destination="-1" eventType="touchUpInside" id="O91-6u-80J"/>
+ </connections>
+ </button>
+ <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="4BU-Kb-3Zx">
+ <rect key="frame" x="0.0" y="54" width="600" height="546"/>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <connections>
+ <outlet property="dataSource" destination="U72-zk-rRG" id="GFT-r0-uBK"/>
+ <outlet property="delegate" destination="U72-zk-rRG" id="uzl-Rm-2qd"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="rRb-jS-QTO" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="8" id="1PF-3b-y4w"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="5Tz-J0-2Dq"/>
+ <constraint firstItem="rRb-jS-QTO" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="16" id="K7t-0g-JBo"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="top" secondItem="rRb-jS-QTO" secondAttribute="bottom" constant="8" id="MyI-Bl-nGS"/>
+ <constraint firstAttribute="bottom" secondItem="4BU-Kb-3Zx" secondAttribute="bottom" id="QMm-Wv-q60"/>
+ <constraint firstItem="4BU-Kb-3Zx" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="deo-wi-Nmq"/>
+ </constraints>
+ </view>
+ <customObject id="U72-zk-rRG" customClass="StaticContentTableViewManager">
+ <connections>
+ <outlet property="tableView" destination="4BU-Kb-3Zx" id="0OK-KI-QdH"/>
+ </connections>
+ </customObject>
+ </objects>
+</document>
diff --git a/AuthSamples/Sample/UserTableViewCell.h b/AuthSamples/Sample/UserTableViewCell.h
new file mode 100644
index 0000000..072b455
--- /dev/null
+++ b/AuthSamples/Sample/UserTableViewCell.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <UIKit/UIKit.h>
+
+@class FIRUser;
+
+/** @class UserTableViewCell
+ @brief Represents a user in a table view.
+ */
+@interface UserTableViewCell : UITableViewCell
+
+/** @property userInfoProfileURLImageView
+ @brief A UIImageView whose image is set to the user's profile URL.
+ */
+@property(nonatomic, weak) IBOutlet UIImageView *userInfoProfileURLImageView;
+
+/** @property userInfoDisplayNameLabel
+ @brief A UILabel whose text is set to the user's display name.
+ */
+@property(nonatomic, weak) IBOutlet UILabel *userInfoDisplayNameLabel;
+
+/** @property userInfoEmailLabel
+ @brief A UILabel whose text is set to the user's email.
+ */
+@property(nonatomic, weak) IBOutlet UILabel *userInfoEmailLabel;
+
+/** @property userInfoUserIDLabel
+ @brief A UILabel whose text is set to the user's User ID.
+ */
+@property(nonatomic, weak) IBOutlet UILabel *userInfoUserIDLabel;
+
+/** @property userInfoProviderListLabel
+ @brief A UILabel whose text is set to the user's comma-delimited list of federated sign in
+ provider IDs.
+ */
+@property(nonatomic, weak) IBOutlet UILabel *userInfoProviderListLabel;
+
+/** @fn updateContentsWithUser:
+ @brief Updates the values of the controls on this table view cell to represent the user.
+ @param user The user whose values should be used to populate this cell.
+ */
+- (void)updateContentsWithUser:(FIRUser *)user;
+
+@end
diff --git a/AuthSamples/Sample/UserTableViewCell.m b/AuthSamples/Sample/UserTableViewCell.m
new file mode 100644
index 0000000..fef3025
--- /dev/null
+++ b/AuthSamples/Sample/UserTableViewCell.m
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "UserTableViewCell.h"
+
+#import "FIRUser.h"
+
+@implementation UserTableViewCell {
+ /** @var _lastPhotoURL
+ @brief Used to make sure only the last requested image is used to update the UIImageView.
+ */
+ NSURL *_lastPhotoURL;
+}
+
+- (void)updateContentsWithUser:(FIRUser *)user {
+ _userInfoDisplayNameLabel.text = user.displayName;
+ _userInfoEmailLabel.text = user.email;
+ _userInfoUserIDLabel.text = user.uid;
+
+ NSMutableArray<NSString *> *providerIDs = [NSMutableArray array];
+ for (id<FIRUserInfo> userInfo in user.providerData) {
+ [providerIDs addObject:userInfo.providerID];
+ }
+ _userInfoProviderListLabel.text = [providerIDs componentsJoinedByString:@", "];
+
+ NSURL *photoURL = user.photoURL;
+ _lastPhotoURL = photoURL; // to prevent eariler image overwrites later one.
+ if (photoURL) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^() {
+ UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:photoURL]];
+ dispatch_async(dispatch_get_main_queue(), ^() {
+ if (photoURL == _lastPhotoURL) {
+ _userInfoProfileURLImageView.image = image;
+ }
+ });
+ });
+ } else {
+ _userInfoProfileURLImageView.image = nil;
+ }
+}
+
+@end
diff --git a/AuthSamples/Sample/main.m b/AuthSamples/Sample/main.m
new file mode 100644
index 0000000..1eadced
--- /dev/null
+++ b/AuthSamples/Sample/main.m
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "ApplicationDelegate.h"
+
+int main(int argc, char *argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([ApplicationDelegate class]));
+ }
+}
diff --git a/AuthSamples/Samples.xcodeproj/project.pbxproj b/AuthSamples/Samples.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..963eabe
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/project.pbxproj
@@ -0,0 +1,1806 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ DE5C700E1EA17F6900A965D2 /* AllTests */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = DE5C700F1EA17F6900A965D2 /* Build configuration list for PBXAggregateTarget "AllTests" */;
+ buildPhases = (
+ );
+ dependencies = (
+ DE5C70131EA17F7200A965D2 /* PBXTargetDependency */,
+ DE5C70151EA17F7200A965D2 /* PBXTargetDependency */,
+ DE5C70171EA17F7200A965D2 /* PBXTargetDependency */,
+ );
+ name = AllTests;
+ productName = AllTests;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 569C3F4E18627674CABE02AE /* Pods_EarlGreyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEE2E563FADF8C3382956B4F /* Pods_EarlGreyTests.framework */; };
+ 67AFFB52FF0FC4668D92F2E4 /* Pods_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5FE06BD9AA795DFBA9EFAAD /* Pods_Sample.framework */; };
+ A7609DCAD8A247411F27EA14 /* Pods_TestApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDD2401395E91D0923BC5CD8 /* Pods_TestApp.framework */; };
+ AB62D09AF8C1196E07F37D3B /* Pods_SwiftBear.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1F689EE8E0E6F83D82429F0 /* Pods_SwiftBear.framework */; };
+ BD555A1DCF4E889DC3338248 /* Pods_FirebaseAuthUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FFAD3F37BC4D7CEF0CAD579 /* Pods_FirebaseAuthUnitTests.framework */; };
+ BE7B447C1EC2508300FA4C1B /* AuthCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE7B447A1EC2507800FA4C1B /* AuthCredentials.swift */; };
+ DE5371B31EA7E89D000DA57F /* FIRAdditionalUserInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371831EA7E89D000DA57F /* FIRAdditionalUserInfoTests.m */; };
+ DE5371B41EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371851EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.m */; };
+ DE5371B51EA7E89D000DA57F /* FIRAuthAppCredentialTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371861EA7E89D000DA57F /* FIRAuthAppCredentialTests.m */; };
+ DE5371B61EA7E89D000DA57F /* FIRAuthAppDelegateProxyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371871EA7E89D000DA57F /* FIRAuthAppDelegateProxyTests.m */; };
+ DE5371B71EA7E89D000DA57F /* FIRAuthBackendCreateAuthURITests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371881EA7E89D000DA57F /* FIRAuthBackendCreateAuthURITests.m */; };
+ DE5371B81EA7E89D000DA57F /* FIRAuthBackendRPCImplementationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371891EA7E89D000DA57F /* FIRAuthBackendRPCImplementationTests.m */; };
+ DE5371B91EA7E89D000DA57F /* FIRAuthDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718A1EA7E89D000DA57F /* FIRAuthDispatcherTests.m */; };
+ DE5371BA1EA7E89D000DA57F /* FIRAuthGlobalWorkQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718B1EA7E89D000DA57F /* FIRAuthGlobalWorkQueueTests.m */; };
+ DE5371BB1EA7E89D000DA57F /* FIRAuthKeychainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718C1EA7E89D000DA57F /* FIRAuthKeychainTests.m */; };
+ DE5371BC1EA7E89D000DA57F /* FIRAuthSerialTaskQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718D1EA7E89D000DA57F /* FIRAuthSerialTaskQueueTests.m */; };
+ DE5371BD1EA7E89D000DA57F /* FIRAuthTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718E1EA7E89D000DA57F /* FIRAuthTests.m */; };
+ DE5371BE1EA7E89D000DA57F /* FIRAuthUserDefaultsStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53718F1EA7E89D000DA57F /* FIRAuthUserDefaultsStorageTests.m */; };
+ DE5371BF1EA7E89D000DA57F /* FIRCreateAuthURIRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371901EA7E89D000DA57F /* FIRCreateAuthURIRequestTests.m */; };
+ DE5371C01EA7E89D000DA57F /* FIRCreateAuthURIResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371911EA7E89D000DA57F /* FIRCreateAuthURIResponseTests.m */; };
+ DE5371C11EA7E89D000DA57F /* FIRDeleteAccountRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371921EA7E89D000DA57F /* FIRDeleteAccountRequestTests.m */; };
+ DE5371C21EA7E89D000DA57F /* FIRDeleteAccountResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371931EA7E89D000DA57F /* FIRDeleteAccountResponseTests.m */; };
+ DE5371C31EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371951EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.m */; };
+ DE5371C41EA7E89D000DA57F /* FIRGetAccountInfoRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371961EA7E89D000DA57F /* FIRGetAccountInfoRequestTests.m */; };
+ DE5371C51EA7E89D000DA57F /* FIRGetAccountInfoResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371971EA7E89D000DA57F /* FIRGetAccountInfoResponseTests.m */; };
+ DE5371C61EA7E89D000DA57F /* FIRGetOOBConfirmationCodeRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371981EA7E89D000DA57F /* FIRGetOOBConfirmationCodeRequestTests.m */; };
+ DE5371C71EA7E89D000DA57F /* FIRGetOOBConfirmationCodeResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371991EA7E89D000DA57F /* FIRGetOOBConfirmationCodeResponseTests.m */; };
+ DE5371C81EA7E89D000DA57F /* FIRGitHubAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719A1EA7E89D000DA57F /* FIRGitHubAuthProviderTests.m */; };
+ DE5371C91EA7E89D000DA57F /* FIRPhoneAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719B1EA7E89D000DA57F /* FIRPhoneAuthProviderTests.m */; };
+ DE5371CA1EA7E89D000DA57F /* FIRResetPasswordRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719C1EA7E89D000DA57F /* FIRResetPasswordRequestTests.m */; };
+ DE5371CB1EA7E89D000DA57F /* FIRResetPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719D1EA7E89D000DA57F /* FIRResetPasswordResponseTests.m */; };
+ DE5371CC1EA7E89D000DA57F /* FIRSendVerificationCodeRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719E1EA7E89D000DA57F /* FIRSendVerificationCodeRequestTests.m */; };
+ DE5371CD1EA7E89D000DA57F /* FIRSendVerificationCodeResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE53719F1EA7E89D000DA57F /* FIRSendVerificationCodeResponseTests.m */; };
+ DE5371CE1EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A01EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m */; };
+ DE5371CF1EA7E89D000DA57F /* FIRSetAccountInfoResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A11EA7E89D000DA57F /* FIRSetAccountInfoResponseTests.m */; };
+ DE5371D01EA7E89D000DA57F /* FIRSignUpNewUserRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A21EA7E89D000DA57F /* FIRSignUpNewUserRequestTests.m */; };
+ DE5371D11EA7E89D000DA57F /* FIRSignUpNewUserResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A31EA7E89D000DA57F /* FIRSignUpNewUserResponseTests.m */; };
+ DE5371D21EA7E89D000DA57F /* FIRTwitterAuthProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A41EA7E89D000DA57F /* FIRTwitterAuthProviderTests.m */; };
+ DE5371D31EA7E89D000DA57F /* FIRUserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A51EA7E89D000DA57F /* FIRUserTests.m */; };
+ DE5371D41EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A61EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m */; };
+ DE5371D51EA7E89D000DA57F /* FIRVerifyAssertionResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A71EA7E89D000DA57F /* FIRVerifyAssertionResponseTests.m */; };
+ DE5371D61EA7E89D000DA57F /* FIRVerifyClientRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A81EA7E89D000DA57F /* FIRVerifyClientRequestTest.m */; };
+ DE5371D71EA7E89D000DA57F /* FIRVerifyClientResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371A91EA7E89D000DA57F /* FIRVerifyClientResponseTests.m */; };
+ DE5371D81EA7E89D000DA57F /* FIRVerifyCustomTokenRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AA1EA7E89D000DA57F /* FIRVerifyCustomTokenRequestTests.m */; };
+ DE5371D91EA7E89D000DA57F /* FIRVerifyCustomTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AB1EA7E89D000DA57F /* FIRVerifyCustomTokenResponseTests.m */; };
+ DE5371DA1EA7E89D000DA57F /* FIRVerifyPasswordRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AC1EA7E89D000DA57F /* FIRVerifyPasswordRequestTest.m */; };
+ DE5371DB1EA7E89D000DA57F /* FIRVerifyPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AD1EA7E89D000DA57F /* FIRVerifyPasswordResponseTests.m */; };
+ DE5371DC1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AE1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m */; };
+ DE5371DD1EA7E89D000DA57F /* FIRVerifyPhoneNumberResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371AF1EA7E89D000DA57F /* FIRVerifyPhoneNumberResponseTests.m */; };
+ DE5371DE1EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5371B11EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m */; };
+ DE5371DF1EA7E89D000DA57F /* Tests-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE5371B21EA7E89D000DA57F /* Tests-Info.plist */; };
+ DECE04E21E9FEAE600164CA4 /* Application.plist in Resources */ = {isa = PBXBuildFile; fileRef = DECE049B1E9FEAE600164CA4 /* Application.plist */; };
+ DECE04E31E9FEAE600164CA4 /* ApplicationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE049D1E9FEAE600164CA4 /* ApplicationDelegate.m */; };
+ DECE04E41E9FEAE600164CA4 /* AuthProviders.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE049F1E9FEAE600164CA4 /* AuthProviders.m */; };
+ DECE04E51E9FEAE600164CA4 /* CustomTokenDataEntryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04A21E9FEAE600164CA4 /* CustomTokenDataEntryViewController.m */; };
+ DECE04E61E9FEAE600164CA4 /* FacebookAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04A41E9FEAE600164CA4 /* FacebookAuthProvider.m */; };
+ DECE04E71E9FEAE600164CA4 /* GoogleAuthProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04A61E9FEAE600164CA4 /* GoogleAuthProvider.m */; };
+ DECE04E91E9FEAE600164CA4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DECE04A81E9FEAE600164CA4 /* GoogleService-Info.plist */; };
+ DECE04EA1E9FEAE600164CA4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DECE04A91E9FEAE600164CA4 /* Images.xcassets */; };
+ DECE04EB1E9FEAE600164CA4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04AA1E9FEAE600164CA4 /* main.m */; };
+ DECE04EC1E9FEAE600164CA4 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04AC1E9FEAE600164CA4 /* MainViewController.m */; };
+ DECE04ED1E9FEAE600164CA4 /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DECE04AD1E9FEAE600164CA4 /* MainViewController.xib */; };
+ DECE04EF1E9FEAE600164CA4 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04B01E9FEAE600164CA4 /* SettingsViewController.m */; };
+ DECE04F01E9FEAE600164CA4 /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DECE04B11E9FEAE600164CA4 /* SettingsViewController.xib */; };
+ DECE04F11E9FEAE600164CA4 /* StaticContentTableViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04B31E9FEAE600164CA4 /* StaticContentTableViewManager.m */; };
+ DECE04F21E9FEAE600164CA4 /* FirebearSample.strings in Resources */ = {isa = PBXBuildFile; fileRef = DECE04B51E9FEAE600164CA4 /* FirebearSample.strings */; };
+ DECE04F31E9FEAE600164CA4 /* FirebaseAuthUI.strings in Resources */ = {isa = PBXBuildFile; fileRef = DECE04BC1E9FEAE600164CA4 /* FirebaseAuthUI.strings */; };
+ DECE04F41E9FEAE600164CA4 /* UIViewController+Alerts.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04DC1E9FEAE600164CA4 /* UIViewController+Alerts.m */; };
+ DECE04F51E9FEAE600164CA4 /* UserInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04DE1E9FEAE600164CA4 /* UserInfoViewController.m */; };
+ DECE04F61E9FEAE600164CA4 /* UserInfoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DECE04DF1E9FEAE600164CA4 /* UserInfoViewController.xib */; };
+ DECE04F71E9FEAE600164CA4 /* UserTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DECE04E11E9FEAE600164CA4 /* UserTableViewCell.m */; };
+ DECEA56C1EBBED1200273585 /* FIRAuthAPNSTokenManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DECEA5681EBBED1200273585 /* FIRAuthAPNSTokenManagerTests.m */; };
+ DECEA56D1EBBED1200273585 /* FIRAuthAPNSTokenTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DECEA5691EBBED1200273585 /* FIRAuthAPNSTokenTests.m */; };
+ DECEA56E1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DECEA56A1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m */; };
+ DECEA56F1EBBED1200273585 /* FIRAuthNotificationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DECEA56B1EBBED1200273585 /* FIRAuthNotificationManagerTests.m */; };
+ DEE13A051E9FFC9500D1BABA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE13A041E9FFC9500D1BABA /* AppDelegate.swift */; };
+ DEE13A071E9FFC9500D1BABA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE13A061E9FFC9500D1BABA /* ViewController.swift */; };
+ DEE13A161E9FFD1F00D1BABA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEE13A141E9FFD1F00D1BABA /* LaunchScreen.storyboard */; };
+ DEE13A171E9FFD1F00D1BABA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEE13A151E9FFD1F00D1BABA /* Main.storyboard */; };
+ DEE13A1A1E9FFD2E00D1BABA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEE13A191E9FFD2E00D1BABA /* GoogleService-Info.plist */; };
+ DEE13A2B1EA125CD00D1BABA /* FirebearApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE13A2A1EA125CD00D1BABA /* FirebearApiTests.m */; };
+ DEE13A3D1EA164E100D1BABA /* FirebearEarlGreyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE13A3C1EA164E100D1BABA /* FirebearEarlGreyTests.m */; };
+ DEE13AA21EA1724300D1BABA /* FirebaseDev.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE13A2C1EA12B8D00D1BABA /* FirebaseDev.framework */; };
+ DEE13ACB1EA1764B00D1BABA /* Auth-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEE13ABE1EA1764B00D1BABA /* Auth-Info.plist */; };
+ DEE13ACC1EA1764B00D1BABA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEE13AC01EA1764B00D1BABA /* LaunchScreen.storyboard */; };
+ DEE13ACD1EA1764B00D1BABA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEE13AC21EA1764B00D1BABA /* Main.storyboard */; };
+ DEE13ACE1EA1764B00D1BABA /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE13AC51EA1764B00D1BABA /* FIRAppDelegate.m */; };
+ DEE13ACF1EA1764B00D1BABA /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE13AC71EA1764B00D1BABA /* FIRViewController.m */; };
+ DEE13AD01EA1764B00D1BABA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEE13AC81EA1764B00D1BABA /* GoogleService-Info.plist */; };
+ DEE13AD11EA1764B00D1BABA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEE13AC91EA1764B00D1BABA /* Images.xcassets */; };
+ DEE13AD21EA1764B00D1BABA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE13ACA1EA1764B00D1BABA /* main.m */; };
+ E7989F47679257E9190C787F /* Pods_ApiTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC09307D636721EAAB89BB2 /* Pods_ApiTests.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ DE5C70121EA17F7200A965D2 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEE13A1F1EA1252D00D1BABA;
+ remoteInfo = ApiTests;
+ };
+ DE5C70141EA17F7200A965D2 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEE13A311EA1642A00D1BABA;
+ remoteInfo = EarlGreyTests;
+ };
+ DE5C70161EA17F7200A965D2 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEE13A411EA16EE400D1BABA;
+ remoteInfo = FirebaseAuthUnitTests;
+ };
+ DEA41FF51EA17A030072DA74 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEE13AA61EA1761B00D1BABA;
+ remoteInfo = TestApp;
+ };
+ DEE13A251EA1252D00D1BABA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DECE04831E9FEA7500164CA4;
+ remoteInfo = Sample;
+ };
+ DEE13A371EA1642A00D1BABA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DECE04831E9FEA7500164CA4;
+ remoteInfo = Sample;
+ };
+ DEE13A471EA16EE400D1BABA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DECE045E1E9FEA1000164CA4 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DECE04831E9FEA7500164CA4;
+ remoteInfo = Sample;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 113C36392871524143A53B07 /* Pods-ApiTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ApiTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ApiTests/Pods-ApiTests.debug.xcconfig"; sourceTree = "<group>"; };
+ 151B5A61F0B0D152CD55DF81 /* Pods-SwiftBear.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBear.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBear/Pods-SwiftBear.release.xcconfig"; sourceTree = "<group>"; };
+ 178AA1A3F0690CDEC44E2BF1 /* Pods-FirebaseAuthUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseAuthUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseAuthUnitTests/Pods-FirebaseAuthUnitTests.release.xcconfig"; sourceTree = "<group>"; };
+ 1B16EC50B5315C8E9E2D21CE /* Pods-EarlGreyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EarlGreyTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-EarlGreyTests/Pods-EarlGreyTests.debug.xcconfig"; sourceTree = "<group>"; };
+ 36794820176319C88B4F11E5 /* Pods-SwiftBear.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBear.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBear/Pods-SwiftBear.debug.xcconfig"; sourceTree = "<group>"; };
+ 495F22BEDDFD4DFE3FB2D522 /* Pods-SwiftSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSample/Pods-SwiftSample.debug.xcconfig"; sourceTree = "<group>"; };
+ 4FFAD3F37BC4D7CEF0CAD579 /* Pods_FirebaseAuthUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseAuthUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 57150555A6B03949ECB58AD9 /* Pods-TestApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TestApp/Pods-TestApp.debug.xcconfig"; sourceTree = "<group>"; };
+ 6EC09307D636721EAAB89BB2 /* Pods_ApiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ApiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 81ED9C5F2E61472DE3FA17CC /* Pods-ApiTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ApiTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ApiTests/Pods-ApiTests.release.xcconfig"; sourceTree = "<group>"; };
+ 920E926BD468CBC593349A36 /* Pods-FirebaseAuthUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseAuthUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseAuthUnitTests/Pods-FirebaseAuthUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
+ 94E3B3EB70D34E55CFF2E45D /* Pods-EarlGreyTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EarlGreyTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-EarlGreyTests/Pods-EarlGreyTests.release.xcconfig"; sourceTree = "<group>"; };
+ A1F689EE8E0E6F83D82429F0 /* Pods_SwiftBear.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftBear.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ A75CAF1D0796E27A3E899DE4 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = "<group>"; };
+ AEE2E563FADF8C3382956B4F /* Pods_EarlGreyTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EarlGreyTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ B78FD2B21A8D72D5E38E3E79 /* Pods-TestApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-TestApp/Pods-TestApp.release.xcconfig"; sourceTree = "<group>"; };
+ BC8C39EF1F42A0C750FF5186 /* Pods_SwiftSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ BE7B447A1EC2507800FA4C1B /* AuthCredentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthCredentials.swift; sourceTree = "<group>"; };
+ BED403DD1EBC057E00885C2C /* AuthCredentialsTemplate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuthCredentialsTemplate.h; sourceTree = "<group>"; };
+ CDD2401395E91D0923BC5CD8 /* Pods_TestApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TestApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D1EE09E5B9A6C092236222A9 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = "<group>"; };
+ D5FE06BD9AA795DFBA9EFAAD /* Pods_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D9D6F0DE0BB3F49EF1B7CBB3 /* Pods-SwiftSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftSample/Pods-SwiftSample.release.xcconfig"; sourceTree = "<group>"; };
+ DE5371831EA7E89D000DA57F /* FIRAdditionalUserInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAdditionalUserInfoTests.m; path = ../../Example/Auth/Tests/FIRAdditionalUserInfoTests.m; sourceTree = "<group>"; };
+ DE5371841EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FIRApp+FIRAuthUnitTests.h"; path = "../../Example/Auth/Tests/FIRApp+FIRAuthUnitTests.h"; sourceTree = "<group>"; };
+ DE5371851EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FIRApp+FIRAuthUnitTests.m"; path = "../../Example/Auth/Tests/FIRApp+FIRAuthUnitTests.m"; sourceTree = "<group>"; };
+ DE5371861EA7E89D000DA57F /* FIRAuthAppCredentialTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAppCredentialTests.m; path = ../../Example/Auth/Tests/FIRAuthAppCredentialTests.m; sourceTree = "<group>"; };
+ DE5371871EA7E89D000DA57F /* FIRAuthAppDelegateProxyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAppDelegateProxyTests.m; path = ../../Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m; sourceTree = "<group>"; };
+ DE5371881EA7E89D000DA57F /* FIRAuthBackendCreateAuthURITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthBackendCreateAuthURITests.m; path = ../../Example/Auth/Tests/FIRAuthBackendCreateAuthURITests.m; sourceTree = "<group>"; };
+ DE5371891EA7E89D000DA57F /* FIRAuthBackendRPCImplementationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthBackendRPCImplementationTests.m; path = ../../Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m; sourceTree = "<group>"; };
+ DE53718A1EA7E89D000DA57F /* FIRAuthDispatcherTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthDispatcherTests.m; path = ../../Example/Auth/Tests/FIRAuthDispatcherTests.m; sourceTree = "<group>"; };
+ DE53718B1EA7E89D000DA57F /* FIRAuthGlobalWorkQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthGlobalWorkQueueTests.m; path = ../../Example/Auth/Tests/FIRAuthGlobalWorkQueueTests.m; sourceTree = "<group>"; };
+ DE53718C1EA7E89D000DA57F /* FIRAuthKeychainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthKeychainTests.m; path = ../../Example/Auth/Tests/FIRAuthKeychainTests.m; sourceTree = "<group>"; };
+ DE53718D1EA7E89D000DA57F /* FIRAuthSerialTaskQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthSerialTaskQueueTests.m; path = ../../Example/Auth/Tests/FIRAuthSerialTaskQueueTests.m; sourceTree = "<group>"; };
+ DE53718E1EA7E89D000DA57F /* FIRAuthTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthTests.m; path = ../../Example/Auth/Tests/FIRAuthTests.m; sourceTree = "<group>"; };
+ DE53718F1EA7E89D000DA57F /* FIRAuthUserDefaultsStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthUserDefaultsStorageTests.m; path = ../../Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m; sourceTree = "<group>"; };
+ DE5371901EA7E89D000DA57F /* FIRCreateAuthURIRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRCreateAuthURIRequestTests.m; path = ../../Example/Auth/Tests/FIRCreateAuthURIRequestTests.m; sourceTree = "<group>"; };
+ DE5371911EA7E89D000DA57F /* FIRCreateAuthURIResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRCreateAuthURIResponseTests.m; path = ../../Example/Auth/Tests/FIRCreateAuthURIResponseTests.m; sourceTree = "<group>"; };
+ DE5371921EA7E89D000DA57F /* FIRDeleteAccountRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRDeleteAccountRequestTests.m; path = ../../Example/Auth/Tests/FIRDeleteAccountRequestTests.m; sourceTree = "<group>"; };
+ DE5371931EA7E89D000DA57F /* FIRDeleteAccountResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRDeleteAccountResponseTests.m; path = ../../Example/Auth/Tests/FIRDeleteAccountResponseTests.m; sourceTree = "<group>"; };
+ DE5371941EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FIRFakeBackendRPCIssuer.h; path = ../../Example/Auth/Tests/FIRFakeBackendRPCIssuer.h; sourceTree = "<group>"; };
+ DE5371951EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRFakeBackendRPCIssuer.m; path = ../../Example/Auth/Tests/FIRFakeBackendRPCIssuer.m; sourceTree = "<group>"; };
+ DE5371961EA7E89D000DA57F /* FIRGetAccountInfoRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGetAccountInfoRequestTests.m; path = ../../Example/Auth/Tests/FIRGetAccountInfoRequestTests.m; sourceTree = "<group>"; };
+ DE5371971EA7E89D000DA57F /* FIRGetAccountInfoResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGetAccountInfoResponseTests.m; path = ../../Example/Auth/Tests/FIRGetAccountInfoResponseTests.m; sourceTree = "<group>"; };
+ DE5371981EA7E89D000DA57F /* FIRGetOOBConfirmationCodeRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGetOOBConfirmationCodeRequestTests.m; path = ../../Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m; sourceTree = "<group>"; };
+ DE5371991EA7E89D000DA57F /* FIRGetOOBConfirmationCodeResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGetOOBConfirmationCodeResponseTests.m; path = ../../Example/Auth/Tests/FIRGetOOBConfirmationCodeResponseTests.m; sourceTree = "<group>"; };
+ DE53719A1EA7E89D000DA57F /* FIRGitHubAuthProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGitHubAuthProviderTests.m; path = ../../Example/Auth/Tests/FIRGitHubAuthProviderTests.m; sourceTree = "<group>"; };
+ DE53719B1EA7E89D000DA57F /* FIRPhoneAuthProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRPhoneAuthProviderTests.m; path = ../../Example/Auth/Tests/FIRPhoneAuthProviderTests.m; sourceTree = "<group>"; };
+ DE53719C1EA7E89D000DA57F /* FIRResetPasswordRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRResetPasswordRequestTests.m; path = ../../Example/Auth/Tests/FIRResetPasswordRequestTests.m; sourceTree = "<group>"; };
+ DE53719D1EA7E89D000DA57F /* FIRResetPasswordResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRResetPasswordResponseTests.m; path = ../../Example/Auth/Tests/FIRResetPasswordResponseTests.m; sourceTree = "<group>"; };
+ DE53719E1EA7E89D000DA57F /* FIRSendVerificationCodeRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSendVerificationCodeRequestTests.m; path = ../../Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m; sourceTree = "<group>"; };
+ DE53719F1EA7E89D000DA57F /* FIRSendVerificationCodeResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSendVerificationCodeResponseTests.m; path = ../../Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m; sourceTree = "<group>"; };
+ DE5371A01EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSetAccountInfoRequestTests.m; path = ../../Example/Auth/Tests/FIRSetAccountInfoRequestTests.m; sourceTree = "<group>"; };
+ DE5371A11EA7E89D000DA57F /* FIRSetAccountInfoResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSetAccountInfoResponseTests.m; path = ../../Example/Auth/Tests/FIRSetAccountInfoResponseTests.m; sourceTree = "<group>"; };
+ DE5371A21EA7E89D000DA57F /* FIRSignUpNewUserRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSignUpNewUserRequestTests.m; path = ../../Example/Auth/Tests/FIRSignUpNewUserRequestTests.m; sourceTree = "<group>"; };
+ DE5371A31EA7E89D000DA57F /* FIRSignUpNewUserResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRSignUpNewUserResponseTests.m; path = ../../Example/Auth/Tests/FIRSignUpNewUserResponseTests.m; sourceTree = "<group>"; };
+ DE5371A41EA7E89D000DA57F /* FIRTwitterAuthProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRTwitterAuthProviderTests.m; path = ../../Example/Auth/Tests/FIRTwitterAuthProviderTests.m; sourceTree = "<group>"; };
+ DE5371A51EA7E89D000DA57F /* FIRUserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRUserTests.m; path = ../../Example/Auth/Tests/FIRUserTests.m; sourceTree = "<group>"; };
+ DE5371A61EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyAssertionRequestTests.m; path = ../../Example/Auth/Tests/FIRVerifyAssertionRequestTests.m; sourceTree = "<group>"; };
+ DE5371A71EA7E89D000DA57F /* FIRVerifyAssertionResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyAssertionResponseTests.m; path = ../../Example/Auth/Tests/FIRVerifyAssertionResponseTests.m; sourceTree = "<group>"; };
+ DE5371A81EA7E89D000DA57F /* FIRVerifyClientRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyClientRequestTest.m; path = ../../Example/Auth/Tests/FIRVerifyClientRequestTest.m; sourceTree = "<group>"; };
+ DE5371A91EA7E89D000DA57F /* FIRVerifyClientResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyClientResponseTests.m; path = ../../Example/Auth/Tests/FIRVerifyClientResponseTests.m; sourceTree = "<group>"; };
+ DE5371AA1EA7E89D000DA57F /* FIRVerifyCustomTokenRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyCustomTokenRequestTests.m; path = ../../Example/Auth/Tests/FIRVerifyCustomTokenRequestTests.m; sourceTree = "<group>"; };
+ DE5371AB1EA7E89D000DA57F /* FIRVerifyCustomTokenResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyCustomTokenResponseTests.m; path = ../../Example/Auth/Tests/FIRVerifyCustomTokenResponseTests.m; sourceTree = "<group>"; };
+ DE5371AC1EA7E89D000DA57F /* FIRVerifyPasswordRequestTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPasswordRequestTest.m; path = ../../Example/Auth/Tests/FIRVerifyPasswordRequestTest.m; sourceTree = "<group>"; };
+ DE5371AD1EA7E89D000DA57F /* FIRVerifyPasswordResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPasswordResponseTests.m; path = ../../Example/Auth/Tests/FIRVerifyPasswordResponseTests.m; sourceTree = "<group>"; };
+ DE5371AE1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPhoneNumberRequestTests.m; path = ../../Example/Auth/Tests/FIRVerifyPhoneNumberRequestTests.m; sourceTree = "<group>"; };
+ DE5371AF1EA7E89D000DA57F /* FIRVerifyPhoneNumberResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRVerifyPhoneNumberResponseTests.m; path = ../../Example/Auth/Tests/FIRVerifyPhoneNumberResponseTests.m; sourceTree = "<group>"; };
+ DE5371B01EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "OCMStubRecorder+FIRAuthUnitTests.h"; path = "../../Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.h"; sourceTree = "<group>"; };
+ DE5371B11EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "OCMStubRecorder+FIRAuthUnitTests.m"; path = "../../Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.m"; sourceTree = "<group>"; };
+ DE5371B21EA7E89D000DA57F /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Tests-Info.plist"; path = "../../Example/Auth/Tests/Tests-Info.plist"; sourceTree = "<group>"; };
+ DECE04841E9FEA7500164CA4 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DECE049B1E9FEAE600164CA4 /* Application.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Application.plist; sourceTree = "<group>"; };
+ DECE049C1E9FEAE600164CA4 /* ApplicationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplicationDelegate.h; sourceTree = "<group>"; };
+ DECE049D1E9FEAE600164CA4 /* ApplicationDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplicationDelegate.m; sourceTree = "<group>"; };
+ DECE049E1E9FEAE600164CA4 /* AuthProviders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthProviders.h; sourceTree = "<group>"; };
+ DECE049F1E9FEAE600164CA4 /* AuthProviders.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthProviders.m; sourceTree = "<group>"; };
+ DECE04A11E9FEAE600164CA4 /* CustomTokenDataEntryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTokenDataEntryViewController.h; sourceTree = "<group>"; };
+ DECE04A21E9FEAE600164CA4 /* CustomTokenDataEntryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomTokenDataEntryViewController.m; sourceTree = "<group>"; };
+ DECE04A31E9FEAE600164CA4 /* FacebookAuthProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookAuthProvider.h; sourceTree = "<group>"; };
+ DECE04A41E9FEAE600164CA4 /* FacebookAuthProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FacebookAuthProvider.m; sourceTree = "<group>"; };
+ DECE04A51E9FEAE600164CA4 /* GoogleAuthProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleAuthProvider.h; sourceTree = "<group>"; };
+ DECE04A61E9FEAE600164CA4 /* GoogleAuthProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleAuthProvider.m; sourceTree = "<group>"; };
+ DECE04A81E9FEAE600164CA4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
+ DECE04A91E9FEAE600164CA4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
+ DECE04AA1E9FEAE600164CA4 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DECE04AB1E9FEAE600164CA4 /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = "<group>"; };
+ DECE04AC1E9FEAE600164CA4 /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = "<group>"; };
+ DECE04AD1E9FEAE600164CA4 /* MainViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainViewController.xib; sourceTree = "<group>"; };
+ DECE04AF1E9FEAE600164CA4 /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = "<group>"; };
+ DECE04B01E9FEAE600164CA4 /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = "<group>"; };
+ DECE04B11E9FEAE600164CA4 /* SettingsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsViewController.xib; sourceTree = "<group>"; };
+ DECE04B21E9FEAE600164CA4 /* StaticContentTableViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StaticContentTableViewManager.h; sourceTree = "<group>"; };
+ DECE04B31E9FEAE600164CA4 /* StaticContentTableViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StaticContentTableViewManager.m; sourceTree = "<group>"; };
+ DECE04B61E9FEAE600164CA4 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04B71E9FEAE600164CA4 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04B81E9FEAE600164CA4 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04B91E9FEAE600164CA4 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04BA1E9FEAE600164CA4 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04BB1E9FEAE600164CA4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04BD1E9FEAE600164CA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/FirebaseAuthUI.strings; sourceTree = "<group>"; };
+ DECE04BE1E9FEAE600164CA4 /* en_GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_GB; path = en_GB.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04BF1E9FEAE600164CA4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C01E9FEAE600164CA4 /* es_MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es_MX; path = es_MX.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C11E9FEAE600164CA4 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C21E9FEAE600164CA4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C31E9FEAE600164CA4 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C41E9FEAE600164CA4 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C51E9FEAE600164CA4 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C61E9FEAE600164CA4 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C71E9FEAE600164CA4 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C81E9FEAE600164CA4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04C91E9FEAE600164CA4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CA1E9FEAE600164CA4 /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CB1E9FEAE600164CA4 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CC1E9FEAE600164CA4 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CD1E9FEAE600164CA4 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CE1E9FEAE600164CA4 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04CF1E9FEAE600164CA4 /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D01E9FEAE600164CA4 /* pt_PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_PT; path = pt_PT.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D11E9FEAE600164CA4 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D21E9FEAE600164CA4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D31E9FEAE600164CA4 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D41E9FEAE600164CA4 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D51E9FEAE600164CA4 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D61E9FEAE600164CA4 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D71E9FEAE600164CA4 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D81E9FEAE600164CA4 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04D91E9FEAE600164CA4 /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04DA1E9FEAE600164CA4 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = zh_TW.lproj/FirebearSample.strings; sourceTree = "<group>"; };
+ DECE04DB1E9FEAE600164CA4 /* UIViewController+Alerts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Alerts.h"; sourceTree = "<group>"; };
+ DECE04DC1E9FEAE600164CA4 /* UIViewController+Alerts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Alerts.m"; sourceTree = "<group>"; };
+ DECE04DD1E9FEAE600164CA4 /* UserInfoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserInfoViewController.h; sourceTree = "<group>"; };
+ DECE04DE1E9FEAE600164CA4 /* UserInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserInfoViewController.m; sourceTree = "<group>"; };
+ DECE04DF1E9FEAE600164CA4 /* UserInfoViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserInfoViewController.xib; sourceTree = "<group>"; };
+ DECE04E01E9FEAE600164CA4 /* UserTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserTableViewCell.h; sourceTree = "<group>"; };
+ DECE04E11E9FEAE600164CA4 /* UserTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserTableViewCell.m; sourceTree = "<group>"; };
+ DECEA5661EBBDFB400273585 /* AuthCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthCredentials.h; sourceTree = "<group>"; };
+ DECEA5681EBBED1200273585 /* FIRAuthAPNSTokenManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAPNSTokenManagerTests.m; path = ../../Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m; sourceTree = "<group>"; };
+ DECEA5691EBBED1200273585 /* FIRAuthAPNSTokenTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAPNSTokenTests.m; path = ../../Example/Auth/Tests/FIRAuthAPNSTokenTests.m; sourceTree = "<group>"; };
+ DECEA56A1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthAppCredentialManagerTests.m; path = ../../Example/Auth/Tests/FIRAuthAppCredentialManagerTests.m; sourceTree = "<group>"; };
+ DECEA56B1EBBED1200273585 /* FIRAuthNotificationManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthNotificationManagerTests.m; path = ../../Example/Auth/Tests/FIRAuthNotificationManagerTests.m; sourceTree = "<group>"; };
+ DEE13A021E9FFC9500D1BABA /* SwiftBear.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftBear.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEE13A041E9FFC9500D1BABA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+ DEE13A061E9FFC9500D1BABA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
+ DEE13A101E9FFC9500D1BABA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEE13A141E9FFD1F00D1BABA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
+ DEE13A151E9FFD1F00D1BABA /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DEE13A191E9FFD2E00D1BABA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
+ DEE13A201EA1252D00D1BABA /* ApiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ApiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEE13A241EA1252D00D1BABA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEE13A2A1EA125CD00D1BABA /* FirebearApiTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebearApiTests.m; sourceTree = "<group>"; };
+ DEE13A2C1EA12B8D00D1BABA /* FirebaseDev.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseDev.framework; path = "../../../Library/Developer/Xcode/DerivedData/Samples-dzyyktlzbrpvmobgpcqwaobjmibu/Build/Products/Debug-iphonesimulator/FirebaseDev/FirebaseDev.framework"; sourceTree = "<group>"; };
+ DEE13A321EA1642A00D1BABA /* EarlGreyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EarlGreyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEE13A361EA1642A00D1BABA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEE13A3C1EA164E100D1BABA /* FirebearEarlGreyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebearEarlGreyTests.m; sourceTree = "<group>"; };
+ DEE13A421EA16EE400D1BABA /* FirebaseAuthUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseAuthUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEE13AA71EA1761B00D1BABA /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEE13ABE1EA1764B00D1BABA /* Auth-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Auth-Info.plist"; path = "../../Example/Auth/App/Auth-Info.plist"; sourceTree = "<group>"; };
+ DEE13AC11EA1764B00D1BABA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
+ DEE13AC31EA1764B00D1BABA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = "<group>"; };
+ DEE13AC41EA1764B00D1BABA /* FIRAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FIRAppDelegate.h; path = ../../Example/Auth/App/FIRAppDelegate.h; sourceTree = "<group>"; };
+ DEE13AC51EA1764B00D1BABA /* FIRAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAppDelegate.m; path = ../../Example/Auth/App/FIRAppDelegate.m; sourceTree = "<group>"; };
+ DEE13AC61EA1764B00D1BABA /* FIRViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FIRViewController.h; path = ../../Example/Auth/App/FIRViewController.h; sourceTree = "<group>"; };
+ DEE13AC71EA1764B00D1BABA /* FIRViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRViewController.m; path = ../../Example/Auth/App/FIRViewController.m; sourceTree = "<group>"; };
+ DEE13AC81EA1764B00D1BABA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../Example/Auth/App/GoogleService-Info.plist"; sourceTree = "<group>"; };
+ DEE13AC91EA1764B00D1BABA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ../../Example/Auth/App/Images.xcassets; sourceTree = "<group>"; };
+ DEE13ACA1EA1764B00D1BABA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ../../Example/Auth/App/main.m; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DECE04811E9FEA7500164CA4 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 67AFFB52FF0FC4668D92F2E4 /* Pods_Sample.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE139FF1E9FFC9500D1BABA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AB62D09AF8C1196E07F37D3B /* Pods_SwiftBear.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A1D1EA1252D00D1BABA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E7989F47679257E9190C787F /* Pods_ApiTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A2F1EA1642A00D1BABA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 569C3F4E18627674CABE02AE /* Pods_EarlGreyTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A3F1EA16EE400D1BABA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13AA21EA1724300D1BABA /* FirebaseDev.framework in Frameworks */,
+ BD555A1DCF4E889DC3338248 /* Pods_FirebaseAuthUnitTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13AA41EA1761B00D1BABA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A7609DCAD8A247411F27EA14 /* Pods_TestApp.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 2BC00404B97D81ACA2DF9DE8 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ D1EE09E5B9A6C092236222A9 /* Pods-Sample.debug.xcconfig */,
+ A75CAF1D0796E27A3E899DE4 /* Pods-Sample.release.xcconfig */,
+ 495F22BEDDFD4DFE3FB2D522 /* Pods-SwiftSample.debug.xcconfig */,
+ D9D6F0DE0BB3F49EF1B7CBB3 /* Pods-SwiftSample.release.xcconfig */,
+ 113C36392871524143A53B07 /* Pods-ApiTests.debug.xcconfig */,
+ 81ED9C5F2E61472DE3FA17CC /* Pods-ApiTests.release.xcconfig */,
+ 36794820176319C88B4F11E5 /* Pods-SwiftBear.debug.xcconfig */,
+ 151B5A61F0B0D152CD55DF81 /* Pods-SwiftBear.release.xcconfig */,
+ 1B16EC50B5315C8E9E2D21CE /* Pods-EarlGreyTests.debug.xcconfig */,
+ 94E3B3EB70D34E55CFF2E45D /* Pods-EarlGreyTests.release.xcconfig */,
+ 920E926BD468CBC593349A36 /* Pods-FirebaseAuthUnitTests.debug.xcconfig */,
+ 178AA1A3F0690CDEC44E2BF1 /* Pods-FirebaseAuthUnitTests.release.xcconfig */,
+ 57150555A6B03949ECB58AD9 /* Pods-TestApp.debug.xcconfig */,
+ B78FD2B21A8D72D5E38E3E79 /* Pods-TestApp.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "<group>";
+ };
+ DECE045D1E9FEA1000164CA4 = {
+ isa = PBXGroup;
+ children = (
+ DECE04851E9FEA7500164CA4 /* Sample */,
+ DEE13A031E9FFC9500D1BABA /* SwiftSample */,
+ DEE13A211EA1252D00D1BABA /* ApiTests */,
+ DEE13A331EA1642A00D1BABA /* EarlGreyTests */,
+ DEE13A431EA16EE400D1BABA /* FirebaseAuthUnitTests */,
+ DEE13AA81EA1761B00D1BABA /* TestApp */,
+ DECE04671E9FEA1000164CA4 /* Products */,
+ 2BC00404B97D81ACA2DF9DE8 /* Pods */,
+ FC34A5565C2ACFA8C5AA3B1E /* Frameworks */,
+ );
+ sourceTree = "<group>";
+ };
+ DECE04671E9FEA1000164CA4 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DECE04841E9FEA7500164CA4 /* Sample.app */,
+ DEE13A021E9FFC9500D1BABA /* SwiftBear.app */,
+ DEE13A201EA1252D00D1BABA /* ApiTests.xctest */,
+ DEE13A321EA1642A00D1BABA /* EarlGreyTests.xctest */,
+ DEE13A421EA16EE400D1BABA /* FirebaseAuthUnitTests.xctest */,
+ DEE13AA71EA1761B00D1BABA /* TestApp.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ DECE04851E9FEA7500164CA4 /* Sample */ = {
+ isa = PBXGroup;
+ children = (
+ DECEA5661EBBDFB400273585 /* AuthCredentials.h */,
+ DECE049B1E9FEAE600164CA4 /* Application.plist */,
+ DECE049C1E9FEAE600164CA4 /* ApplicationDelegate.h */,
+ BED403DD1EBC057E00885C2C /* AuthCredentialsTemplate.h */,
+ DECE049D1E9FEAE600164CA4 /* ApplicationDelegate.m */,
+ DECE049E1E9FEAE600164CA4 /* AuthProviders.h */,
+ DECE049F1E9FEAE600164CA4 /* AuthProviders.m */,
+ DECE04A01E9FEAE600164CA4 /* Base.lproj */,
+ DECE04A11E9FEAE600164CA4 /* CustomTokenDataEntryViewController.h */,
+ DECE04A21E9FEAE600164CA4 /* CustomTokenDataEntryViewController.m */,
+ DECE04A31E9FEAE600164CA4 /* FacebookAuthProvider.h */,
+ DECE04A41E9FEAE600164CA4 /* FacebookAuthProvider.m */,
+ DECE04A51E9FEAE600164CA4 /* GoogleAuthProvider.h */,
+ DECE04A61E9FEAE600164CA4 /* GoogleAuthProvider.m */,
+ DECE04A81E9FEAE600164CA4 /* GoogleService-Info.plist */,
+ DECE04A91E9FEAE600164CA4 /* Images.xcassets */,
+ DECE04AA1E9FEAE600164CA4 /* main.m */,
+ DECE04AB1E9FEAE600164CA4 /* MainViewController.h */,
+ DECE04AC1E9FEAE600164CA4 /* MainViewController.m */,
+ DECE04AD1E9FEAE600164CA4 /* MainViewController.xib */,
+ DECE04AF1E9FEAE600164CA4 /* SettingsViewController.h */,
+ DECE04B01E9FEAE600164CA4 /* SettingsViewController.m */,
+ DECE04B11E9FEAE600164CA4 /* SettingsViewController.xib */,
+ DECE04B21E9FEAE600164CA4 /* StaticContentTableViewManager.h */,
+ DECE04B31E9FEAE600164CA4 /* StaticContentTableViewManager.m */,
+ DECE04B41E9FEAE600164CA4 /* Strings */,
+ DECE04DB1E9FEAE600164CA4 /* UIViewController+Alerts.h */,
+ DECE04DC1E9FEAE600164CA4 /* UIViewController+Alerts.m */,
+ DECE04DD1E9FEAE600164CA4 /* UserInfoViewController.h */,
+ DECE04DE1E9FEAE600164CA4 /* UserInfoViewController.m */,
+ DECE04DF1E9FEAE600164CA4 /* UserInfoViewController.xib */,
+ DECE04E01E9FEAE600164CA4 /* UserTableViewCell.h */,
+ DECE04E11E9FEAE600164CA4 /* UserTableViewCell.m */,
+ );
+ path = Sample;
+ sourceTree = "<group>";
+ };
+ DECE04A01E9FEAE600164CA4 /* Base.lproj */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Base.lproj;
+ sourceTree = "<group>";
+ };
+ DECE04B41E9FEAE600164CA4 /* Strings */ = {
+ isa = PBXGroup;
+ children = (
+ DECE04B51E9FEAE600164CA4 /* FirebearSample.strings */,
+ DECE04BC1E9FEAE600164CA4 /* FirebaseAuthUI.strings */,
+ );
+ path = Strings;
+ sourceTree = "<group>";
+ };
+ DEE13A031E9FFC9500D1BABA /* SwiftSample */ = {
+ isa = PBXGroup;
+ children = (
+ BE7B447A1EC2507800FA4C1B /* AuthCredentials.swift */,
+ DEE13A181E9FFD2E00D1BABA /* Base.lproj */,
+ DEE13A191E9FFD2E00D1BABA /* GoogleService-Info.plist */,
+ DEE13A141E9FFD1F00D1BABA /* LaunchScreen.storyboard */,
+ DEE13A151E9FFD1F00D1BABA /* Main.storyboard */,
+ DEE13A041E9FFC9500D1BABA /* AppDelegate.swift */,
+ DEE13A061E9FFC9500D1BABA /* ViewController.swift */,
+ DEE13A101E9FFC9500D1BABA /* Info.plist */,
+ );
+ path = SwiftSample;
+ sourceTree = "<group>";
+ };
+ DEE13A181E9FFD2E00D1BABA /* Base.lproj */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Base.lproj;
+ sourceTree = "<group>";
+ };
+ DEE13A211EA1252D00D1BABA /* ApiTests */ = {
+ isa = PBXGroup;
+ children = (
+ DEE13A2A1EA125CD00D1BABA /* FirebearApiTests.m */,
+ DEE13A241EA1252D00D1BABA /* Info.plist */,
+ );
+ path = ApiTests;
+ sourceTree = "<group>";
+ };
+ DEE13A331EA1642A00D1BABA /* EarlGreyTests */ = {
+ isa = PBXGroup;
+ children = (
+ DEE13A3C1EA164E100D1BABA /* FirebearEarlGreyTests.m */,
+ DEE13A361EA1642A00D1BABA /* Info.plist */,
+ );
+ path = EarlGreyTests;
+ sourceTree = "<group>";
+ };
+ DEE13A431EA16EE400D1BABA /* FirebaseAuthUnitTests */ = {
+ isa = PBXGroup;
+ children = (
+ DECEA5681EBBED1200273585 /* FIRAuthAPNSTokenManagerTests.m */,
+ DECEA5691EBBED1200273585 /* FIRAuthAPNSTokenTests.m */,
+ DECEA56A1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m */,
+ DECEA56B1EBBED1200273585 /* FIRAuthNotificationManagerTests.m */,
+ DE5371831EA7E89D000DA57F /* FIRAdditionalUserInfoTests.m */,
+ DE5371841EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.h */,
+ DE5371851EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.m */,
+ DE5371861EA7E89D000DA57F /* FIRAuthAppCredentialTests.m */,
+ DE5371871EA7E89D000DA57F /* FIRAuthAppDelegateProxyTests.m */,
+ DE5371881EA7E89D000DA57F /* FIRAuthBackendCreateAuthURITests.m */,
+ DE5371891EA7E89D000DA57F /* FIRAuthBackendRPCImplementationTests.m */,
+ DE53718A1EA7E89D000DA57F /* FIRAuthDispatcherTests.m */,
+ DE53718B1EA7E89D000DA57F /* FIRAuthGlobalWorkQueueTests.m */,
+ DE53718C1EA7E89D000DA57F /* FIRAuthKeychainTests.m */,
+ DE53718D1EA7E89D000DA57F /* FIRAuthSerialTaskQueueTests.m */,
+ DE53718E1EA7E89D000DA57F /* FIRAuthTests.m */,
+ DE53718F1EA7E89D000DA57F /* FIRAuthUserDefaultsStorageTests.m */,
+ DE5371901EA7E89D000DA57F /* FIRCreateAuthURIRequestTests.m */,
+ DE5371911EA7E89D000DA57F /* FIRCreateAuthURIResponseTests.m */,
+ DE5371921EA7E89D000DA57F /* FIRDeleteAccountRequestTests.m */,
+ DE5371931EA7E89D000DA57F /* FIRDeleteAccountResponseTests.m */,
+ DE5371941EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.h */,
+ DE5371951EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.m */,
+ DE5371961EA7E89D000DA57F /* FIRGetAccountInfoRequestTests.m */,
+ DE5371971EA7E89D000DA57F /* FIRGetAccountInfoResponseTests.m */,
+ DE5371981EA7E89D000DA57F /* FIRGetOOBConfirmationCodeRequestTests.m */,
+ DE5371991EA7E89D000DA57F /* FIRGetOOBConfirmationCodeResponseTests.m */,
+ DE53719A1EA7E89D000DA57F /* FIRGitHubAuthProviderTests.m */,
+ DE53719B1EA7E89D000DA57F /* FIRPhoneAuthProviderTests.m */,
+ DE53719C1EA7E89D000DA57F /* FIRResetPasswordRequestTests.m */,
+ DE53719D1EA7E89D000DA57F /* FIRResetPasswordResponseTests.m */,
+ DE53719E1EA7E89D000DA57F /* FIRSendVerificationCodeRequestTests.m */,
+ DE53719F1EA7E89D000DA57F /* FIRSendVerificationCodeResponseTests.m */,
+ DE5371A01EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m */,
+ DE5371A11EA7E89D000DA57F /* FIRSetAccountInfoResponseTests.m */,
+ DE5371A21EA7E89D000DA57F /* FIRSignUpNewUserRequestTests.m */,
+ DE5371A31EA7E89D000DA57F /* FIRSignUpNewUserResponseTests.m */,
+ DE5371A41EA7E89D000DA57F /* FIRTwitterAuthProviderTests.m */,
+ DE5371A51EA7E89D000DA57F /* FIRUserTests.m */,
+ DE5371A61EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m */,
+ DE5371A71EA7E89D000DA57F /* FIRVerifyAssertionResponseTests.m */,
+ DE5371A81EA7E89D000DA57F /* FIRVerifyClientRequestTest.m */,
+ DE5371A91EA7E89D000DA57F /* FIRVerifyClientResponseTests.m */,
+ DE5371AA1EA7E89D000DA57F /* FIRVerifyCustomTokenRequestTests.m */,
+ DE5371AB1EA7E89D000DA57F /* FIRVerifyCustomTokenResponseTests.m */,
+ DE5371AC1EA7E89D000DA57F /* FIRVerifyPasswordRequestTest.m */,
+ DE5371AD1EA7E89D000DA57F /* FIRVerifyPasswordResponseTests.m */,
+ DE5371AE1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m */,
+ DE5371AF1EA7E89D000DA57F /* FIRVerifyPhoneNumberResponseTests.m */,
+ DE5371B01EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.h */,
+ DE5371B11EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m */,
+ DE5371B21EA7E89D000DA57F /* Tests-Info.plist */,
+ );
+ path = FirebaseAuthUnitTests;
+ sourceTree = "<group>";
+ };
+ DEE13AA81EA1761B00D1BABA /* TestApp */ = {
+ isa = PBXGroup;
+ children = (
+ DEE13ABE1EA1764B00D1BABA /* Auth-Info.plist */,
+ DEE13ABF1EA1764B00D1BABA /* Base.lproj */,
+ DEE13AC41EA1764B00D1BABA /* FIRAppDelegate.h */,
+ DEE13AC51EA1764B00D1BABA /* FIRAppDelegate.m */,
+ DEE13AC61EA1764B00D1BABA /* FIRViewController.h */,
+ DEE13AC71EA1764B00D1BABA /* FIRViewController.m */,
+ DEE13AC81EA1764B00D1BABA /* GoogleService-Info.plist */,
+ DEE13AC91EA1764B00D1BABA /* Images.xcassets */,
+ DEE13ACA1EA1764B00D1BABA /* main.m */,
+ );
+ path = TestApp;
+ sourceTree = "<group>";
+ };
+ DEE13ABF1EA1764B00D1BABA /* Base.lproj */ = {
+ isa = PBXGroup;
+ children = (
+ DEE13AC01EA1764B00D1BABA /* LaunchScreen.storyboard */,
+ DEE13AC21EA1764B00D1BABA /* Main.storyboard */,
+ );
+ name = Base.lproj;
+ path = ../../Example/Auth/App/Base.lproj;
+ sourceTree = "<group>";
+ };
+ FC34A5565C2ACFA8C5AA3B1E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ DEE13A2C1EA12B8D00D1BABA /* FirebaseDev.framework */,
+ D5FE06BD9AA795DFBA9EFAAD /* Pods_Sample.framework */,
+ BC8C39EF1F42A0C750FF5186 /* Pods_SwiftSample.framework */,
+ 6EC09307D636721EAAB89BB2 /* Pods_ApiTests.framework */,
+ A1F689EE8E0E6F83D82429F0 /* Pods_SwiftBear.framework */,
+ AEE2E563FADF8C3382956B4F /* Pods_EarlGreyTests.framework */,
+ 4FFAD3F37BC4D7CEF0CAD579 /* Pods_FirebaseAuthUnitTests.framework */,
+ CDD2401395E91D0923BC5CD8 /* Pods_TestApp.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ DECE04831E9FEA7500164CA4 /* Sample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DECE04981E9FEA7500164CA4 /* Build configuration list for PBXNativeTarget "Sample" */;
+ buildPhases = (
+ 8666ED2AA464450C3FE8DEE2 /* [CP] Check Pods Manifest.lock */,
+ DECE04801E9FEA7500164CA4 /* Sources */,
+ DECE04811E9FEA7500164CA4 /* Frameworks */,
+ DECE04821E9FEA7500164CA4 /* Resources */,
+ 14D1078EE81275B0906224AE /* [CP] Embed Pods Frameworks */,
+ 785E0AFEF965753D65D704A3 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Sample;
+ productName = Sample;
+ productReference = DECE04841E9FEA7500164CA4 /* Sample.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEE13A011E9FFC9500D1BABA /* SwiftBear */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEE13A111E9FFC9500D1BABA /* Build configuration list for PBXNativeTarget "SwiftBear" */;
+ buildPhases = (
+ DD7BB43E0B7B486BDBF34616 /* [CP] Check Pods Manifest.lock */,
+ DEE139FE1E9FFC9500D1BABA /* Sources */,
+ DEE139FF1E9FFC9500D1BABA /* Frameworks */,
+ DEE13A001E9FFC9500D1BABA /* Resources */,
+ B241AC8AA926740AE9FBD0DD /* [CP] Embed Pods Frameworks */,
+ C341173EBB67728BFF59F1A6 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SwiftBear;
+ productName = SwiftSample;
+ productReference = DEE13A021E9FFC9500D1BABA /* SwiftBear.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEE13A1F1EA1252D00D1BABA /* ApiTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEE13A291EA1252D00D1BABA /* Build configuration list for PBXNativeTarget "ApiTests" */;
+ buildPhases = (
+ DECFE0EC98C60CEE719E03CA /* [CP] Check Pods Manifest.lock */,
+ DEE13A1C1EA1252D00D1BABA /* Sources */,
+ DEE13A1D1EA1252D00D1BABA /* Frameworks */,
+ DEE13A1E1EA1252D00D1BABA /* Resources */,
+ C3FCDC32190B715603C4E5EA /* [CP] Embed Pods Frameworks */,
+ D625EF1F1CC955CEFC768099 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEE13A261EA1252D00D1BABA /* PBXTargetDependency */,
+ );
+ name = ApiTests;
+ productName = ApiTests;
+ productReference = DEE13A201EA1252D00D1BABA /* ApiTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEE13A311EA1642A00D1BABA /* EarlGreyTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEE13A391EA1642B00D1BABA /* Build configuration list for PBXNativeTarget "EarlGreyTests" */;
+ buildPhases = (
+ 31C9CD5738CD8A86F3E29FD4 /* [CP] Check Pods Manifest.lock */,
+ DEE13A2E1EA1642A00D1BABA /* Sources */,
+ DEE13A2F1EA1642A00D1BABA /* Frameworks */,
+ DEE13A301EA1642A00D1BABA /* Resources */,
+ 45E0A7D32BD529EA299F169F /* [CP] Embed Pods Frameworks */,
+ C5EB2DFC39A4D32D63305071 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEE13A381EA1642A00D1BABA /* PBXTargetDependency */,
+ );
+ name = EarlGreyTests;
+ productName = EarlGreyTests;
+ productReference = DEE13A321EA1642A00D1BABA /* EarlGreyTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEE13A411EA16EE400D1BABA /* FirebaseAuthUnitTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEE13A4B1EA16EE400D1BABA /* Build configuration list for PBXNativeTarget "FirebaseAuthUnitTests" */;
+ buildPhases = (
+ 19197348303E74218FE9F86A /* [CP] Check Pods Manifest.lock */,
+ DEE13A3E1EA16EE400D1BABA /* Sources */,
+ DEE13A3F1EA16EE400D1BABA /* Frameworks */,
+ DEE13A401EA16EE400D1BABA /* Resources */,
+ F2982E8E37D71A5B31C19526 /* [CP] Embed Pods Frameworks */,
+ A7955AE6B74F92A6A1A61539 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEE13A481EA16EE400D1BABA /* PBXTargetDependency */,
+ DEA41FF61EA17A030072DA74 /* PBXTargetDependency */,
+ );
+ name = FirebaseAuthUnitTests;
+ productName = FirebaseAuthUnitTests;
+ productReference = DEE13A421EA16EE400D1BABA /* FirebaseAuthUnitTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEE13AA61EA1761B00D1BABA /* TestApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEE13ABD1EA1761C00D1BABA /* Build configuration list for PBXNativeTarget "TestApp" */;
+ buildPhases = (
+ 0C85A097DBA90CB3BA988388 /* [CP] Check Pods Manifest.lock */,
+ DEE13AA31EA1761B00D1BABA /* Sources */,
+ DEE13AA41EA1761B00D1BABA /* Frameworks */,
+ DEE13AA51EA1761B00D1BABA /* Resources */,
+ 90FD4EF0F0A764C0793CAA62 /* [CP] Embed Pods Frameworks */,
+ 91A5B60E1FF1F6B7A1979A7E /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = TestApp;
+ productName = TestApp;
+ productReference = DEE13AA71EA1761B00D1BABA /* TestApp.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ DECE045E1E9FEA1000164CA4 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0830;
+ LastUpgradeCheck = 0830;
+ ORGANIZATIONNAME = paulbeusterien;
+ TargetAttributes = {
+ DE5C700E1EA17F6900A965D2 = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ DECE04831E9FEA7500164CA4 = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ LastSwiftMigration = 0830;
+ ProvisioningStyle = Automatic;
+ };
+ DEE13A011E9FFC9500D1BABA = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ DEE13A1F1EA1252D00D1BABA = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DECE04831E9FEA7500164CA4;
+ };
+ DEE13A311EA1642A00D1BABA = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DECE04831E9FEA7500164CA4;
+ };
+ DEE13A411EA16EE400D1BABA = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DEE13AA61EA1761B00D1BABA;
+ };
+ DEE13AA61EA1761B00D1BABA = {
+ CreatedOnToolsVersion = 8.3;
+ DevelopmentTeam = EQHXZ8M8AV;
+ ProvisioningStyle = Automatic;
+ };
+ };
+ };
+ buildConfigurationList = DECE04611E9FEA1000164CA4 /* Build configuration list for PBXProject "Samples" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ ar,
+ ca,
+ cs,
+ da,
+ de,
+ el,
+ en_GB,
+ es,
+ es_MX,
+ fi,
+ fr,
+ he,
+ hr,
+ hu,
+ id,
+ it,
+ ja,
+ ko,
+ ms,
+ nb,
+ nl,
+ pl,
+ pt,
+ pt_BR,
+ pt_PT,
+ ro,
+ ru,
+ sk,
+ sv,
+ th,
+ tr,
+ uk,
+ vi,
+ zh_CN,
+ zh_TW,
+ );
+ mainGroup = DECE045D1E9FEA1000164CA4;
+ productRefGroup = DECE04671E9FEA1000164CA4 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ DECE04831E9FEA7500164CA4 /* Sample */,
+ DEE13A011E9FFC9500D1BABA /* SwiftBear */,
+ DEE13A1F1EA1252D00D1BABA /* ApiTests */,
+ DEE13A311EA1642A00D1BABA /* EarlGreyTests */,
+ DEE13A411EA16EE400D1BABA /* FirebaseAuthUnitTests */,
+ DEE13AA61EA1761B00D1BABA /* TestApp */,
+ DE5C700E1EA17F6900A965D2 /* AllTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ DECE04821E9FEA7500164CA4 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DECE04F21E9FEAE600164CA4 /* FirebearSample.strings in Resources */,
+ DECE04E21E9FEAE600164CA4 /* Application.plist in Resources */,
+ DECE04EA1E9FEAE600164CA4 /* Images.xcassets in Resources */,
+ DECE04ED1E9FEAE600164CA4 /* MainViewController.xib in Resources */,
+ DECE04F31E9FEAE600164CA4 /* FirebaseAuthUI.strings in Resources */,
+ DECE04F61E9FEAE600164CA4 /* UserInfoViewController.xib in Resources */,
+ DECE04F01E9FEAE600164CA4 /* SettingsViewController.xib in Resources */,
+ DECE04E91E9FEAE600164CA4 /* GoogleService-Info.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A001E9FFC9500D1BABA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13A161E9FFD1F00D1BABA /* LaunchScreen.storyboard in Resources */,
+ DEE13A171E9FFD1F00D1BABA /* Main.storyboard in Resources */,
+ DEE13A1A1E9FFD2E00D1BABA /* GoogleService-Info.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A1E1EA1252D00D1BABA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A301EA1642A00D1BABA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A401EA16EE400D1BABA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE5371DF1EA7E89D000DA57F /* Tests-Info.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13AA51EA1761B00D1BABA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13ACD1EA1764B00D1BABA /* Main.storyboard in Resources */,
+ DEE13AD11EA1764B00D1BABA /* Images.xcassets in Resources */,
+ DEE13AD01EA1764B00D1BABA /* GoogleService-Info.plist in Resources */,
+ DEE13ACB1EA1764B00D1BABA /* Auth-Info.plist in Resources */,
+ DEE13ACC1EA1764B00D1BABA /* LaunchScreen.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 0C85A097DBA90CB3BA988388 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ 14D1078EE81275B0906224AE /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 19197348303E74218FE9F86A /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ 31C9CD5738CD8A86F3E29FD4 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ 45E0A7D32BD529EA299F169F /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EarlGreyTests/Pods-EarlGreyTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 785E0AFEF965753D65D704A3 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 8666ED2AA464450C3FE8DEE2 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ 90FD4EF0F0A764C0793CAA62 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-TestApp/Pods-TestApp-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 91A5B60E1FF1F6B7A1979A7E /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-TestApp/Pods-TestApp-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ A7955AE6B74F92A6A1A61539 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseAuthUnitTests/Pods-FirebaseAuthUnitTests-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ B241AC8AA926740AE9FBD0DD /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBear/Pods-SwiftBear-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C341173EBB67728BFF59F1A6 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBear/Pods-SwiftBear-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C3FCDC32190B715603C4E5EA /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ApiTests/Pods-ApiTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C5EB2DFC39A4D32D63305071 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EarlGreyTests/Pods-EarlGreyTests-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ D625EF1F1CC955CEFC768099 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ApiTests/Pods-ApiTests-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ DD7BB43E0B7B486BDBF34616 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ DECFE0EC98C60CEE719E03CA /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ F2982E8E37D71A5B31C19526 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseAuthUnitTests/Pods-FirebaseAuthUnitTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DECE04801E9FEA7500164CA4 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DECE04E61E9FEAE600164CA4 /* FacebookAuthProvider.m in Sources */,
+ DECE04F41E9FEAE600164CA4 /* UIViewController+Alerts.m in Sources */,
+ DECE04E51E9FEAE600164CA4 /* CustomTokenDataEntryViewController.m in Sources */,
+ DECE04E41E9FEAE600164CA4 /* AuthProviders.m in Sources */,
+ DECE04EF1E9FEAE600164CA4 /* SettingsViewController.m in Sources */,
+ DECE04E71E9FEAE600164CA4 /* GoogleAuthProvider.m in Sources */,
+ DECE04F51E9FEAE600164CA4 /* UserInfoViewController.m in Sources */,
+ DECE04EC1E9FEAE600164CA4 /* MainViewController.m in Sources */,
+ DECE04F71E9FEAE600164CA4 /* UserTableViewCell.m in Sources */,
+ DECE04F11E9FEAE600164CA4 /* StaticContentTableViewManager.m in Sources */,
+ DECE04EB1E9FEAE600164CA4 /* main.m in Sources */,
+ DECE04E31E9FEAE600164CA4 /* ApplicationDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE139FE1E9FFC9500D1BABA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13A071E9FFC9500D1BABA /* ViewController.swift in Sources */,
+ BE7B447C1EC2508300FA4C1B /* AuthCredentials.swift in Sources */,
+ DEE13A051E9FFC9500D1BABA /* AppDelegate.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A1C1EA1252D00D1BABA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13A2B1EA125CD00D1BABA /* FirebearApiTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A2E1EA1642A00D1BABA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13A3D1EA164E100D1BABA /* FirebearEarlGreyTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13A3E1EA16EE400D1BABA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DE5371B31EA7E89D000DA57F /* FIRAdditionalUserInfoTests.m in Sources */,
+ DE5371B61EA7E89D000DA57F /* FIRAuthAppDelegateProxyTests.m in Sources */,
+ DE5371DD1EA7E89D000DA57F /* FIRVerifyPhoneNumberResponseTests.m in Sources */,
+ DE5371D31EA7E89D000DA57F /* FIRUserTests.m in Sources */,
+ DE5371BC1EA7E89D000DA57F /* FIRAuthSerialTaskQueueTests.m in Sources */,
+ DECEA56E1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m in Sources */,
+ DE5371DE1EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */,
+ DE5371DC1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m in Sources */,
+ DE5371B51EA7E89D000DA57F /* FIRAuthAppCredentialTests.m in Sources */,
+ DE5371CE1EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m in Sources */,
+ DE5371D41EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m in Sources */,
+ DE5371D91EA7E89D000DA57F /* FIRVerifyCustomTokenResponseTests.m in Sources */,
+ DECEA56F1EBBED1200273585 /* FIRAuthNotificationManagerTests.m in Sources */,
+ DECEA56D1EBBED1200273585 /* FIRAuthAPNSTokenTests.m in Sources */,
+ DE5371D81EA7E89D000DA57F /* FIRVerifyCustomTokenRequestTests.m in Sources */,
+ DE5371BD1EA7E89D000DA57F /* FIRAuthTests.m in Sources */,
+ DE5371C01EA7E89D000DA57F /* FIRCreateAuthURIResponseTests.m in Sources */,
+ DE5371B91EA7E89D000DA57F /* FIRAuthDispatcherTests.m in Sources */,
+ DE5371CF1EA7E89D000DA57F /* FIRSetAccountInfoResponseTests.m in Sources */,
+ DE5371C81EA7E89D000DA57F /* FIRGitHubAuthProviderTests.m in Sources */,
+ DE5371BB1EA7E89D000DA57F /* FIRAuthKeychainTests.m in Sources */,
+ DE5371B71EA7E89D000DA57F /* FIRAuthBackendCreateAuthURITests.m in Sources */,
+ DE5371C51EA7E89D000DA57F /* FIRGetAccountInfoResponseTests.m in Sources */,
+ DE5371B81EA7E89D000DA57F /* FIRAuthBackendRPCImplementationTests.m in Sources */,
+ DE5371DB1EA7E89D000DA57F /* FIRVerifyPasswordResponseTests.m in Sources */,
+ DE5371D71EA7E89D000DA57F /* FIRVerifyClientResponseTests.m in Sources */,
+ DE5371DA1EA7E89D000DA57F /* FIRVerifyPasswordRequestTest.m in Sources */,
+ DE5371CA1EA7E89D000DA57F /* FIRResetPasswordRequestTests.m in Sources */,
+ DE5371D51EA7E89D000DA57F /* FIRVerifyAssertionResponseTests.m in Sources */,
+ DE5371D01EA7E89D000DA57F /* FIRSignUpNewUserRequestTests.m in Sources */,
+ DE5371C41EA7E89D000DA57F /* FIRGetAccountInfoRequestTests.m in Sources */,
+ DE5371B41EA7E89D000DA57F /* FIRApp+FIRAuthUnitTests.m in Sources */,
+ DE5371D61EA7E89D000DA57F /* FIRVerifyClientRequestTest.m in Sources */,
+ DE5371BE1EA7E89D000DA57F /* FIRAuthUserDefaultsStorageTests.m in Sources */,
+ DE5371C91EA7E89D000DA57F /* FIRPhoneAuthProviderTests.m in Sources */,
+ DE5371D11EA7E89D000DA57F /* FIRSignUpNewUserResponseTests.m in Sources */,
+ DE5371C31EA7E89D000DA57F /* FIRFakeBackendRPCIssuer.m in Sources */,
+ DE5371BF1EA7E89D000DA57F /* FIRCreateAuthURIRequestTests.m in Sources */,
+ DE5371CC1EA7E89D000DA57F /* FIRSendVerificationCodeRequestTests.m in Sources */,
+ DECEA56C1EBBED1200273585 /* FIRAuthAPNSTokenManagerTests.m in Sources */,
+ DE5371CD1EA7E89D000DA57F /* FIRSendVerificationCodeResponseTests.m in Sources */,
+ DE5371C71EA7E89D000DA57F /* FIRGetOOBConfirmationCodeResponseTests.m in Sources */,
+ DE5371C21EA7E89D000DA57F /* FIRDeleteAccountResponseTests.m in Sources */,
+ DE5371CB1EA7E89D000DA57F /* FIRResetPasswordResponseTests.m in Sources */,
+ DE5371D21EA7E89D000DA57F /* FIRTwitterAuthProviderTests.m in Sources */,
+ DE5371C11EA7E89D000DA57F /* FIRDeleteAccountRequestTests.m in Sources */,
+ DE5371BA1EA7E89D000DA57F /* FIRAuthGlobalWorkQueueTests.m in Sources */,
+ DE5371C61EA7E89D000DA57F /* FIRGetOOBConfirmationCodeRequestTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEE13AA31EA1761B00D1BABA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEE13ACF1EA1764B00D1BABA /* FIRViewController.m in Sources */,
+ DEE13AD21EA1764B00D1BABA /* main.m in Sources */,
+ DEE13ACE1EA1764B00D1BABA /* FIRAppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ DE5C70131EA17F7200A965D2 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEE13A1F1EA1252D00D1BABA /* ApiTests */;
+ targetProxy = DE5C70121EA17F7200A965D2 /* PBXContainerItemProxy */;
+ };
+ DE5C70151EA17F7200A965D2 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEE13A311EA1642A00D1BABA /* EarlGreyTests */;
+ targetProxy = DE5C70141EA17F7200A965D2 /* PBXContainerItemProxy */;
+ };
+ DE5C70171EA17F7200A965D2 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEE13A411EA16EE400D1BABA /* FirebaseAuthUnitTests */;
+ targetProxy = DE5C70161EA17F7200A965D2 /* PBXContainerItemProxy */;
+ };
+ DEA41FF61EA17A030072DA74 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEE13AA61EA1761B00D1BABA /* TestApp */;
+ targetProxy = DEA41FF51EA17A030072DA74 /* PBXContainerItemProxy */;
+ };
+ DEE13A261EA1252D00D1BABA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DECE04831E9FEA7500164CA4 /* Sample */;
+ targetProxy = DEE13A251EA1252D00D1BABA /* PBXContainerItemProxy */;
+ };
+ DEE13A381EA1642A00D1BABA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DECE04831E9FEA7500164CA4 /* Sample */;
+ targetProxy = DEE13A371EA1642A00D1BABA /* PBXContainerItemProxy */;
+ };
+ DEE13A481EA16EE400D1BABA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DECE04831E9FEA7500164CA4 /* Sample */;
+ targetProxy = DEE13A471EA16EE400D1BABA /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ DECE04B51E9FEAE600164CA4 /* FirebearSample.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DECE04B61E9FEAE600164CA4 /* ar */,
+ DECE04B71E9FEAE600164CA4 /* ca */,
+ DECE04B81E9FEAE600164CA4 /* cs */,
+ DECE04B91E9FEAE600164CA4 /* da */,
+ DECE04BA1E9FEAE600164CA4 /* de */,
+ DECE04BB1E9FEAE600164CA4 /* el */,
+ DECE04BE1E9FEAE600164CA4 /* en_GB */,
+ DECE04BF1E9FEAE600164CA4 /* es */,
+ DECE04C01E9FEAE600164CA4 /* es_MX */,
+ DECE04C11E9FEAE600164CA4 /* fi */,
+ DECE04C21E9FEAE600164CA4 /* fr */,
+ DECE04C31E9FEAE600164CA4 /* he */,
+ DECE04C41E9FEAE600164CA4 /* hr */,
+ DECE04C51E9FEAE600164CA4 /* hu */,
+ DECE04C61E9FEAE600164CA4 /* id */,
+ DECE04C71E9FEAE600164CA4 /* it */,
+ DECE04C81E9FEAE600164CA4 /* ja */,
+ DECE04C91E9FEAE600164CA4 /* ko */,
+ DECE04CA1E9FEAE600164CA4 /* ms */,
+ DECE04CB1E9FEAE600164CA4 /* nb */,
+ DECE04CC1E9FEAE600164CA4 /* nl */,
+ DECE04CD1E9FEAE600164CA4 /* pl */,
+ DECE04CE1E9FEAE600164CA4 /* pt */,
+ DECE04CF1E9FEAE600164CA4 /* pt_BR */,
+ DECE04D01E9FEAE600164CA4 /* pt_PT */,
+ DECE04D11E9FEAE600164CA4 /* ro */,
+ DECE04D21E9FEAE600164CA4 /* ru */,
+ DECE04D31E9FEAE600164CA4 /* sk */,
+ DECE04D41E9FEAE600164CA4 /* sv */,
+ DECE04D51E9FEAE600164CA4 /* th */,
+ DECE04D61E9FEAE600164CA4 /* tr */,
+ DECE04D71E9FEAE600164CA4 /* uk */,
+ DECE04D81E9FEAE600164CA4 /* vi */,
+ DECE04D91E9FEAE600164CA4 /* zh_CN */,
+ DECE04DA1E9FEAE600164CA4 /* zh_TW */,
+ );
+ name = FirebearSample.strings;
+ sourceTree = "<group>";
+ };
+ DECE04BC1E9FEAE600164CA4 /* FirebaseAuthUI.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DECE04BD1E9FEAE600164CA4 /* en */,
+ );
+ name = FirebaseAuthUI.strings;
+ sourceTree = "<group>";
+ };
+ DEE13AC01EA1764B00D1BABA /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEE13AC11EA1764B00D1BABA /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+ DEE13AC21EA1764B00D1BABA /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEE13AC31EA1764B00D1BABA /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ DE5C70101EA17F6900A965D2 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ DE5C70111EA17F6900A965D2 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+ DECE047B1E9FEA1000164CA4 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ DECE047C1E9FEA1000164CA4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ DECE04991E9FEA7500164CA4 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = D1EE09E5B9A6C092236222A9 /* Pods-Sample.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/Sample/Application.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseExperimental1.dev;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Debug;
+ };
+ DECE049A1E9FEA7500164CA4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = A75CAF1D0796E27A3E899DE4 /* Pods-Sample.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/Sample/Application.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseExperimental1.dev;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Release;
+ };
+ DEE13A121E9FFC9500D1BABA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 36794820176319C88B4F11E5 /* Pods-SwiftBear.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = SwiftSample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.SwiftBear;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Debug;
+ };
+ DEE13A131E9FFC9500D1BABA /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 151B5A61F0B0D152CD55DF81 /* Pods-SwiftBear.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = SwiftSample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.SwiftBear;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_VERSION = 3.0;
+ };
+ name = Release;
+ };
+ DEE13A271EA1252D00D1BABA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 113C36392871524143A53B07 /* Pods-ApiTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = ApiTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.ApiTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample";
+ };
+ name = Debug;
+ };
+ DEE13A281EA1252D00D1BABA /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 81ED9C5F2E61472DE3FA17CC /* Pods-ApiTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = ApiTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.ApiTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample";
+ };
+ name = Release;
+ };
+ DEE13A3A1EA1642B00D1BABA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 1B16EC50B5315C8E9E2D21CE /* Pods-EarlGreyTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = EarlGreyTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.EarlGreyTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample";
+ };
+ name = Debug;
+ };
+ DEE13A3B1EA1642B00D1BABA /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 94E3B3EB70D34E55CFF2E45D /* Pods-EarlGreyTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = EarlGreyTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.EarlGreyTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample";
+ };
+ name = Release;
+ };
+ DEE13A491EA16EE400D1BABA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 920E926BD468CBC593349A36 /* Pods-FirebaseAuthUnitTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/RPCs\"",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/Private\"",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/AuthProviders\"",
+ "\"$(SRCROOT)/../Firebase/Core/Private\"",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/../Example/Auth/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseAuthUnitTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp.app/TestApp";
+ };
+ name = Debug;
+ };
+ DEE13A4A1EA16EE400D1BABA /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 178AA1A3F0690CDEC44E2BF1 /* Pods-FirebaseAuthUnitTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/RPCs\"",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/Private\"",
+ "\"$(SRCROOT)/../Firebase/Auth/Source/AuthProviders\"",
+ "\"$(SRCROOT)/../Firebase/Core/Private\"",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/../Example/Auth/Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseAuthUnitTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp.app/TestApp";
+ };
+ name = Release;
+ };
+ DEE13ABB1EA1761C00D1BABA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 57150555A6B03949ECB58AD9 /* Pods-TestApp.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/../Example/Auth/App/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.TestApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ DEE13ABC1EA1761C00D1BABA /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = B78FD2B21A8D72D5E38E3E79 /* Pods-TestApp.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "";
+ DEVELOPMENT_TEAM = EQHXZ8M8AV;
+ INFOPLIST_FILE = "$(SRCROOT)/../Example/Auth/App/Auth-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.google.TestApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ DE5C700F1EA17F6900A965D2 /* Build configuration list for PBXAggregateTarget "AllTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DE5C70101EA17F6900A965D2 /* Debug */,
+ DE5C70111EA17F6900A965D2 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DECE04611E9FEA1000164CA4 /* Build configuration list for PBXProject "Samples" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DECE047B1E9FEA1000164CA4 /* Debug */,
+ DECE047C1E9FEA1000164CA4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DECE04981E9FEA7500164CA4 /* Build configuration list for PBXNativeTarget "Sample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DECE04991E9FEA7500164CA4 /* Debug */,
+ DECE049A1E9FEA7500164CA4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEE13A111E9FFC9500D1BABA /* Build configuration list for PBXNativeTarget "SwiftBear" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEE13A121E9FFC9500D1BABA /* Debug */,
+ DEE13A131E9FFC9500D1BABA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEE13A291EA1252D00D1BABA /* Build configuration list for PBXNativeTarget "ApiTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEE13A271EA1252D00D1BABA /* Debug */,
+ DEE13A281EA1252D00D1BABA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEE13A391EA1642B00D1BABA /* Build configuration list for PBXNativeTarget "EarlGreyTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEE13A3A1EA1642B00D1BABA /* Debug */,
+ DEE13A3B1EA1642B00D1BABA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEE13A4B1EA16EE400D1BABA /* Build configuration list for PBXNativeTarget "FirebaseAuthUnitTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEE13A491EA16EE400D1BABA /* Debug */,
+ DEE13A4A1EA16EE400D1BABA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEE13ABD1EA1761C00D1BABA /* Build configuration list for PBXNativeTarget "TestApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEE13ABB1EA1761C00D1BABA /* Debug */,
+ DEE13ABC1EA1761C00D1BABA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = DECE045E1E9FEA1000164CA4 /* Project object */;
+}
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
new file mode 100644
index 0000000..c00e18c
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE5C700E1EA17F6900A965D2"
+ BuildableName = "AllTests"
+ BlueprintName = "AllTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A1F1EA1252D00D1BABA"
+ BuildableName = "ApiTests.xctest"
+ BlueprintName = "ApiTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A311EA1642A00D1BABA"
+ BuildableName = "EarlGreyTests.xctest"
+ BlueprintName = "EarlGreyTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A411EA16EE400D1BABA"
+ BuildableName = "FirebaseAuthUnitTests.xctest"
+ BlueprintName = "FirebaseAuthUnitTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE5C700E1EA17F6900A965D2"
+ BuildableName = "AllTests"
+ BlueprintName = "AllTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE5C700E1EA17F6900A965D2"
+ BuildableName = "AllTests"
+ BlueprintName = "AllTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DE5C700E1EA17F6900A965D2"
+ BuildableName = "AllTests"
+ BlueprintName = "AllTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/ApiTests.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/ApiTests.xcscheme
new file mode 100644
index 0000000..5973e7d
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/ApiTests.xcscheme
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A1F1EA1252D00D1BABA"
+ BuildableName = "ApiTests.xctest"
+ BlueprintName = "ApiTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/EarlGreyTests.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/EarlGreyTests.xcscheme
new file mode 100644
index 0000000..bc91613
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/EarlGreyTests.xcscheme
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A311EA1642A00D1BABA"
+ BuildableName = "EarlGreyTests.xctest"
+ BlueprintName = "EarlGreyTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/FirebaseAuthUnitTests.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/FirebaseAuthUnitTests.xcscheme
new file mode 100644
index 0000000..8282ca0
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/FirebaseAuthUnitTests.xcscheme
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A411EA16EE400D1BABA"
+ BuildableName = "FirebaseAuthUnitTests.xctest"
+ BlueprintName = "FirebaseAuthUnitTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme
new file mode 100644
index 0000000..36aebc2
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DECE04831E9FEA7500164CA4"
+ BuildableName = "Sample.app"
+ BlueprintName = "Sample"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A1F1EA1252D00D1BABA"
+ BuildableName = "ApiTests.xctest"
+ BlueprintName = "ApiTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A311EA1642A00D1BABA"
+ BuildableName = "EarlGreyTests.xctest"
+ BlueprintName = "EarlGreyTests"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DECE04831E9FEA7500164CA4"
+ BuildableName = "Sample.app"
+ BlueprintName = "Sample"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DECE04831E9FEA7500164CA4"
+ BuildableName = "Sample.app"
+ BlueprintName = "Sample"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DECE04831E9FEA7500164CA4"
+ BuildableName = "Sample.app"
+ BlueprintName = "Sample"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/SwiftBear.xcscheme b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/SwiftBear.xcscheme
new file mode 100644
index 0000000..1869be5
--- /dev/null
+++ b/AuthSamples/Samples.xcodeproj/xcshareddata/xcschemes/SwiftBear.xcscheme
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0830"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A011E9FFC9500D1BABA"
+ BuildableName = "SwiftBear.app"
+ BlueprintName = "SwiftBear"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A011E9FFC9500D1BABA"
+ BuildableName = "SwiftBear.app"
+ BlueprintName = "SwiftBear"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A011E9FFC9500D1BABA"
+ BuildableName = "SwiftBear.app"
+ BlueprintName = "SwiftBear"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEE13A011E9FFC9500D1BABA"
+ BuildableName = "SwiftBear.app"
+ BlueprintName = "SwiftBear"
+ ReferencedContainer = "container:Samples.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/AuthSamples/SwiftSample/AppDelegate.swift b/AuthSamples/SwiftSample/AppDelegate.swift
new file mode 100644
index 0000000..abe6b2a
--- /dev/null
+++ b/AuthSamples/SwiftSample/AppDelegate.swift
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 UIKit
+
+import FirebaseDev // FirebaseCore
+import GoogleSignIn // GoogleSignIn
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ /** @var kGoogleClientID
+ @brief The Google client ID.
+ */
+ private let kGoogleClientID = AuthCredentials.GOOGLE_CLIENT_ID
+
+ // TODO: add Facebook login support as well.
+
+ /** @var kFacebookAppID
+ @brief The Facebook app ID.
+ */
+ private let kFacebookAppID = AuthCredentials.FACEBOOK_APP_ID
+
+ /** @var window
+ @brief The main window of the app.
+ */
+ var window: UIWindow?
+
+ func application(_ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ FirebaseApp.configure()
+ GIDSignIn.sharedInstance().clientID = kGoogleClientID
+ return true
+ }
+
+ @available(iOS 9.0, *)
+ func application(_ application: UIApplication, open url: URL,
+ options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
+ return GIDSignIn.sharedInstance().handle(url,
+ sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String,
+ annotation: nil)
+ }
+
+ func application(_ application: UIApplication, open url: URL, sourceApplication: String?,
+ annotation: Any) -> Bool {
+ return GIDSignIn.sharedInstance().handle(url, sourceApplication: sourceApplication,
+ annotation: annotation)
+ }
+}
diff --git a/AuthSamples/SwiftSample/AuthCredentialsTemplate.swift b/AuthSamples/SwiftSample/AuthCredentialsTemplate.swift
new file mode 100644
index 0000000..f4a3780
--- /dev/null
+++ b/AuthSamples/SwiftSample/AuthCredentialsTemplate.swift
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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.
+ */
+
+
+/*
+ Some of the Auth Credentials needs to be populated for the Sample build to work.
+
+ Please follow the following steps to populate the valid AuthCredentials
+ and copy it to AuthCredentials.swift file
+
+ You will need to replace the following values:
+ $KGOOGLE_CLIENT_ID
+ Get the value of the CLIENT_ID key in the GoogleService-Info.plist file.
+
+ $KFACEBOOK_APP_ID
+ FACEBOOK_APP_ID is the developer's Facebook app's ID, to be used to test the
+ 'Signing in with Facebook' feature of Firebase Auth. Follow the instructions
+ on the Facebook developer site: https://developers.facebook.com/docs/apps/register
+ to obtain the id
+
+ */
+
+import Foundation
+
+
+struct AuthCredentials {
+ static let FACEBOOK_APP_ID = "$KFACEBOOK_APP_ID"
+ static let GOOGLE_CLIENT_ID = "$KGOOGLE_CLIENT_ID"
+}
diff --git a/AuthSamples/SwiftSample/InfoTemplate.plist b/AuthSamples/SwiftSample/InfoTemplate.plist
new file mode 100644
index 0000000..e3c9457
--- /dev/null
+++ b/AuthSamples/SwiftSample/InfoTemplate.plist
@@ -0,0 +1,79 @@
+<!--
+ For this to be a valid plist file replace the following
+ $REVERSE_CLIENT_ID:
+ Value of REVERSED_CLIENT_ID key in the GoogleService-Info.plist file.
+-->
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>com.google.swiftbear</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>com.google.swiftbear</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>$REVERSE_CLIENT_ID</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>$REVERSE_CLIENT_ID</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>LSApplicationQueriesSchemes</key>
+ <array>
+ <string>fbauth2</string>
+ </array>
+</dict>
+</plist>
diff --git a/AuthSamples/SwiftSample/LaunchScreen.storyboard b/AuthSamples/SwiftSample/LaunchScreen.storyboard
new file mode 100644
index 0000000..8326657
--- /dev/null
+++ b/AuthSamples/SwiftSample/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/AuthSamples/SwiftSample/Main.storyboard b/AuthSamples/SwiftSample/Main.storyboard
new file mode 100644
index 0000000..6c60d61
--- /dev/null
+++ b/AuthSamples/SwiftSample/Main.storyboard
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <device id="retina4_7" orientation="portrait">
+ <adaptation id="fullscreen"/>
+ </device>
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SwiftBear" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="f0Y-rP-2xL">
+ <rect key="frame" x="16" y="20" width="100" height="100"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="100" id="1z8-5e-1FT"/>
+ <constraint firstAttribute="width" constant="100" id="dtU-ZR-yuk"/>
+ </constraints>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lWp-nO-ZaW">
+ <rect key="frame" x="116" y="20" width="263" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dql-K9-Pb8">
+ <rect key="frame" x="116" y="45" width="263" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="x6V-xc-ti7">
+ <rect key="frame" x="116" y="70" width="263" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="26Z-WM-zPg">
+ <rect key="frame" x="116" y="95" width="263" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pbc-lK-fyt">
+ <rect key="frame" x="110" y="128" width="249" height="167"/>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="UQZ-I6-nrk"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="TUP-ja-Ewo"/>
+ </connections>
+ </pickerView>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UCG-40-lyD">
+ <rect key="frame" x="121" y="478" width="132" height="53"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="132" id="6tv-Vh-n1Q"/>
+ <constraint firstAttribute="height" constant="53" id="OTC-oP-oh6"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="24"/>
+ <state key="normal" title="Execute"/>
+ <connections>
+ <action selector="execute:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Y7t-ah-5za"/>
+ </connections>
+ </button>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eNd-jh-yrb">
+ <rect key="frame" x="16" y="303" width="163.5" height="167"/>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="XZ2-uf-4BE"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="brP-1Q-rxy"/>
+ </connections>
+ </pickerView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MZv-Jr-rbJ">
+ <rect key="frame" x="195.5" y="303" width="163.5" height="42"/>
+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cJs-WA-Zoh">
+ <rect key="frame" x="195.5" y="387" width="163.5" height="41.5"/>
+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="wEw-yN-DT4">
+ <rect key="frame" x="195.5" y="345" width="163.5" height="42"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="kph-aS-EnS">
+ <rect key="frame" x="195.5" y="428.5" width="163.5" height="41.5"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p1i-PA-aCy">
+ <rect key="frame" x="16" y="128" width="86" height="167"/>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="DRb-wg-dEY"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="mZd-1x-Tz0"/>
+ </connections>
+ </pickerView>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="pbc-lK-fyt" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" multiplier="3/4" constant="-32" id="0zH-Sz-NAW"/>
+ <constraint firstItem="lWp-nO-ZaW" firstAttribute="leading" secondItem="f0Y-rP-2xL" secondAttribute="trailing" id="8gd-NX-K1r"/>
+ <constraint firstItem="cJs-WA-Zoh" firstAttribute="top" secondItem="wEw-yN-DT4" secondAttribute="bottom" id="CFl-wo-Ta4"/>
+ <constraint firstItem="kph-aS-EnS" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="DqA-5n-kgu"/>
+ <constraint firstItem="pbc-lK-fyt" firstAttribute="top" secondItem="f0Y-rP-2xL" secondAttribute="bottom" constant="8" id="EfK-6i-xV4"/>
+ <constraint firstItem="wEw-yN-DT4" firstAttribute="height" secondItem="cJs-WA-Zoh" secondAttribute="height" id="FgK-9k-ah5"/>
+ <constraint firstItem="wEw-yN-DT4" firstAttribute="width" secondItem="cJs-WA-Zoh" secondAttribute="width" id="G1D-hB-bvJ"/>
+ <constraint firstItem="MZv-Jr-rbJ" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" multiplier="1/2" constant="-24" id="GaI-EN-daq"/>
+ <constraint firstItem="26Z-WM-zPg" firstAttribute="centerX" secondItem="x6V-xc-ti7" secondAttribute="centerX" id="HuK-J8-Fpz"/>
+ <constraint firstItem="eNd-jh-yrb" firstAttribute="top" secondItem="pbc-lK-fyt" secondAttribute="bottom" constant="8" id="LVJ-D1-3Vd"/>
+ <constraint firstItem="Dql-K9-Pb8" firstAttribute="centerX" secondItem="lWp-nO-ZaW" secondAttribute="centerX" id="Ne8-Rv-TLV"/>
+ <constraint firstItem="f0Y-rP-2xL" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="PEc-mF-DfU"/>
+ <constraint firstItem="26Z-WM-zPg" firstAttribute="top" secondItem="x6V-xc-ti7" secondAttribute="bottom" id="PvE-LR-R3h"/>
+ <constraint firstItem="cJs-WA-Zoh" firstAttribute="height" secondItem="kph-aS-EnS" secondAttribute="height" id="SW6-bi-RqF"/>
+ <constraint firstItem="cJs-WA-Zoh" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="SiE-1X-xmu"/>
+ <constraint firstItem="p1i-PA-aCy" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="UFI-LW-2pG"/>
+ <constraint firstItem="Dql-K9-Pb8" firstAttribute="height" secondItem="lWp-nO-ZaW" secondAttribute="height" id="UFa-1x-biN"/>
+ <constraint firstItem="UCG-40-lyD" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="UJ5-Yk-u6Z"/>
+ <constraint firstAttribute="trailingMargin" secondItem="lWp-nO-ZaW" secondAttribute="trailing" constant="-20" id="V6f-WE-gEK"/>
+ <constraint firstItem="x6V-xc-ti7" firstAttribute="height" secondItem="Dql-K9-Pb8" secondAttribute="height" id="VrF-29-yZ6"/>
+ <constraint firstItem="pbc-lK-fyt" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" multiplier="0.25" id="a0i-ba-sYb"/>
+ <constraint firstItem="MZv-Jr-rbJ" firstAttribute="top" secondItem="eNd-jh-yrb" secondAttribute="top" id="aC6-BD-zI0"/>
+ <constraint firstItem="x6V-xc-ti7" firstAttribute="centerX" secondItem="Dql-K9-Pb8" secondAttribute="centerX" id="cIk-ax-zhs"/>
+ <constraint firstItem="MZv-Jr-rbJ" firstAttribute="width" secondItem="wEw-yN-DT4" secondAttribute="width" id="crF-HH-d4y"/>
+ <constraint firstItem="26Z-WM-zPg" firstAttribute="width" secondItem="x6V-xc-ti7" secondAttribute="width" id="evU-m7-CI6"/>
+ <constraint firstItem="cJs-WA-Zoh" firstAttribute="width" secondItem="kph-aS-EnS" secondAttribute="width" id="fDS-yd-RnX"/>
+ <constraint firstItem="wEw-yN-DT4" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="fLC-np-N5n"/>
+ <constraint firstItem="kph-aS-EnS" firstAttribute="top" secondItem="cJs-WA-Zoh" secondAttribute="bottom" id="fQZ-1s-seC"/>
+ <constraint firstItem="MZv-Jr-rbJ" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="fuk-vI-lWD"/>
+ <constraint firstItem="eNd-jh-yrb" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" multiplier="1/4" id="gGV-2A-Djo"/>
+ <constraint firstItem="x6V-xc-ti7" firstAttribute="width" secondItem="Dql-K9-Pb8" secondAttribute="width" id="gnn-hK-Tev"/>
+ <constraint firstItem="MZv-Jr-rbJ" firstAttribute="height" secondItem="wEw-yN-DT4" secondAttribute="height" id="iy8-Im-XOx"/>
+ <constraint firstItem="p1i-PA-aCy" firstAttribute="height" secondItem="pbc-lK-fyt" secondAttribute="height" id="kBZ-fH-wyv"/>
+ <constraint firstItem="pbc-lK-fyt" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="kPG-I5-PXU"/>
+ <constraint firstItem="p1i-PA-aCy" firstAttribute="centerY" secondItem="pbc-lK-fyt" secondAttribute="centerY" id="mYj-of-8sB"/>
+ <constraint firstItem="eNd-jh-yrb" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="mvG-uN-2NO"/>
+ <constraint firstItem="26Z-WM-zPg" firstAttribute="height" secondItem="x6V-xc-ti7" secondAttribute="height" id="o9Q-eA-Mgg"/>
+ <constraint firstItem="pbc-lK-fyt" firstAttribute="leading" secondItem="p1i-PA-aCy" secondAttribute="trailing" constant="8" id="ocO-I3-JB4"/>
+ <constraint firstItem="x6V-xc-ti7" firstAttribute="top" secondItem="Dql-K9-Pb8" secondAttribute="bottom" id="p2W-xj-wNc"/>
+ <constraint firstItem="wEw-yN-DT4" firstAttribute="top" secondItem="MZv-Jr-rbJ" secondAttribute="bottom" id="p57-vW-AI1"/>
+ <constraint firstItem="kph-aS-EnS" firstAttribute="bottom" secondItem="eNd-jh-yrb" secondAttribute="bottom" id="pMo-uD-Ium"/>
+ <constraint firstItem="Dql-K9-Pb8" firstAttribute="width" secondItem="lWp-nO-ZaW" secondAttribute="width" id="qIA-Vy-0Ts"/>
+ <constraint firstItem="UCG-40-lyD" firstAttribute="top" secondItem="eNd-jh-yrb" secondAttribute="bottom" constant="8" id="qLA-FE-fz7"/>
+ <constraint firstItem="lWp-nO-ZaW" firstAttribute="height" secondItem="f0Y-rP-2xL" secondAttribute="height" multiplier="0.25" id="s9E-w5-zQ2"/>
+ <constraint firstItem="f0Y-rP-2xL" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="sFw-Yh-RRZ"/>
+ <constraint firstItem="eNd-jh-yrb" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" multiplier="1/2" constant="-24" id="sdf-aZ-Z6h"/>
+ <constraint firstItem="Dql-K9-Pb8" firstAttribute="top" secondItem="lWp-nO-ZaW" secondAttribute="bottom" id="zo6-AD-yBu"/>
+ <constraint firstItem="lWp-nO-ZaW" firstAttribute="top" secondItem="f0Y-rP-2xL" secondAttribute="top" id="ztC-x1-RUf"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="actionPicker" destination="pbc-lK-fyt" id="C5u-X6-0sb"/>
+ <outlet property="actionTypePicker" destination="p1i-PA-aCy" id="EXj-7M-WaM"/>
+ <outlet property="credentialTypePicker" destination="eNd-jh-yrb" id="J4Z-9q-X2Q"/>
+ <outlet property="displayNameLabel" destination="lWp-nO-ZaW" id="4Kn-aZ-lS4"/>
+ <outlet property="emailField" destination="wEw-yN-DT4" id="3dQ-Dt-Vev"/>
+ <outlet property="emailInputLabel" destination="MZv-Jr-rbJ" id="jFy-DW-9Af"/>
+ <outlet property="emailLabel" destination="Dql-K9-Pb8" id="Ap8-hR-VyQ"/>
+ <outlet property="passwordField" destination="kph-aS-EnS" id="clH-Ac-p5S"/>
+ <outlet property="passwordInputLabel" destination="cJs-WA-Zoh" id="QbJ-ri-FOY"/>
+ <outlet property="profileImage" destination="f0Y-rP-2xL" id="Y14-pW-FuW"/>
+ <outlet property="providerListLabel" destination="26Z-WM-zPg" id="dJq-hC-Dw8"/>
+ <outlet property="userIDLabel" destination="x6V-xc-ti7" id="Cqb-bH-R8C"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="304.80000000000001" y="428.63568215892059"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/AuthSamples/SwiftSample/ViewController.swift b/AuthSamples/SwiftSample/ViewController.swift
new file mode 100644
index 0000000..0b7481a
--- /dev/null
+++ b/AuthSamples/SwiftSample/ViewController.swift
@@ -0,0 +1,521 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 UIKit
+
+import FirebaseDev // FirebaseAuth
+import GoogleSignIn // GoogleSignIn
+
+final class ViewController: UIViewController {
+ /// The profile image for the currently signed-in user.
+ @IBOutlet weak var profileImage: UIImageView!
+
+ /// The display name for the currently signed-in user.
+ @IBOutlet weak var displayNameLabel: UILabel!
+
+ /// The email for the currently signed-in user.
+ @IBOutlet weak var emailLabel: UILabel!
+
+ /// The ID for the currently signed-in user.
+ @IBOutlet weak var userIDLabel: UILabel!
+
+ /// The list of providers for the currently signed-in user.
+ @IBOutlet weak var providerListLabel: UILabel!
+
+ /// The picker for the list of action types.
+ @IBOutlet weak var actionTypePicker: UIPickerView!
+
+ /// The picker for the list of actions.
+ @IBOutlet weak var actionPicker: UIPickerView!
+
+ /// The picker for the list of credential types.
+ @IBOutlet weak var credentialTypePicker: UIPickerView!
+
+ /// The label for the "email" text field.
+ @IBOutlet weak var emailInputLabel: UILabel!
+
+ /// The "email" text field.
+ @IBOutlet weak var emailField: UITextField!
+
+ /// The label for the "password" text field.
+ @IBOutlet weak var passwordInputLabel: UILabel!
+
+ /// The "password" text field.
+ @IBOutlet weak var passwordField: UITextField!
+
+ /// The currently selected action type.
+ fileprivate var actionType = ActionType(rawValue: 0)! {
+ didSet {
+ if actionType != oldValue {
+ actionPicker.reloadAllComponents()
+ actionPicker.selectRow(actionType == .auth ? authAction.rawValue : userAction.rawValue,
+ inComponent: 0, animated: false)
+ }
+ }
+ }
+
+ /// The currently selected auth action.
+ fileprivate var authAction = AuthAction(rawValue: 0)!
+
+ /// The currently selected user action.
+ fileprivate var userAction = UserAction(rawValue: 0)!
+
+ /// The currently selected credential.
+ fileprivate var credentialType = CredentialType(rawValue: 0)!
+
+ /// The current Firebase user.
+ fileprivate var user: User? = nil {
+ didSet {
+ if user?.uid != oldValue?.uid {
+ actionTypePicker.reloadAllComponents()
+ actionType = ActionType(rawValue: actionTypePicker.selectedRow(inComponent: 0))!
+ }
+ }
+ }
+
+ /// The user's photo URL used by the last network request for its contents.
+ fileprivate var lastPhotoURL: URL? = nil
+
+ override func viewDidLoad() {
+ GIDSignIn.sharedInstance().uiDelegate = self
+ updateUserInfo(Auth.auth())
+ NotificationCenter.default.addObserver(forName: .AuthStateDidChange,
+ object: Auth.auth(), queue: nil) { notification in
+ self.updateUserInfo(notification.object as? Auth)
+ }
+ }
+
+ /// Executes the action designated by the operator on the UI.
+ @IBAction func execute(_ sender: UIButton) {
+ switch actionType {
+ case .auth:
+ switch authAction {
+ case .fetchProviderForEmail:
+ Auth.auth().fetchProviders(forEmail: emailField.text!) { providers, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Providers", message: providers?.joined(separator: ", "))
+ }
+ }
+ case .signInAnonymously:
+ Auth.auth().signInAnonymously() { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In Anonymously")
+ }
+ }
+ case .signInWithCredential:
+ getCredential() { credential in
+ Auth.auth().signIn(with: credential) { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In With Credential", message: user?.textDescription)
+ }
+ }
+ }
+ case .createUser:
+ Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!) {
+ user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In With Credential", message: user?.textDescription)
+ }
+ }
+ case .signOut:
+ try! Auth.auth().signOut()
+ GIDSignIn.sharedInstance().signOut()
+ }
+ case .user:
+ switch userAction {
+ case .updateEmail:
+ user!.updateEmail(to: emailField.text!) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Updated Email", message: self.user?.email)
+ }
+ }
+ case .updatePassword:
+ user!.updatePassword(to: passwordField.text!) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Updated Password")
+ }
+ }
+ case .reload:
+ user!.reload() { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Reloaded", message: self.user?.textDescription)
+ }
+ }
+ case .reauthenticate:
+ getCredential() { credential in
+ self.user!.reauthenticate(with: credential) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Reauthenticated", message: self.user?.textDescription)
+ }
+ }
+ }
+ case .getToken:
+ user!.getIDToken() { token, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Got ID Token", message: token)
+ }
+ }
+ case .linkWithCredential:
+ getCredential() { credential in
+ self.user!.link(with: credential) { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Linked With Credential", message: user?.textDescription)
+ }
+ }
+ }
+ case .deleteAccount:
+ user!.delete() { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Deleted Account")
+ }
+ }
+ }
+ }
+ }
+
+ /// Gets an AuthCredential potentially asynchronously.
+ private func getCredential(completion: @escaping (AuthCredential) -> Void) {
+ switch credentialType {
+ case .google:
+ GIDSignIn.sharedInstance().delegate = GoogleSignInDelegate(completion: { user, error in
+ self.ifNoError(error) {
+ completion(GoogleAuthProvider.credential(
+ withIDToken: user!.authentication.idToken,
+ accessToken: user!.authentication.accessToken))
+ }
+ })
+ GIDSignIn.sharedInstance().signIn()
+ case .password:
+ completion(EmailAuthProvider.credential(withEmail: emailField.text!,
+ password: passwordField.text!))
+ }
+ }
+
+ /// Updates user's profile image and info text.
+ private func updateUserInfo(_ auth: Auth?) {
+ user = auth?.currentUser
+ displayNameLabel.text = user?.displayName
+ emailLabel.text = user?.email
+ userIDLabel.text = user?.uid
+ let providers = user?.providerData.map { userInfo in userInfo.providerID }
+ providerListLabel.text = providers?.joined(separator: ", ")
+ if let photoURL = user?.photoURL {
+ lastPhotoURL = photoURL
+ DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).async {
+ if let imageData = try? Data(contentsOf: photoURL) {
+ let image = UIImage(data: imageData)
+ DispatchQueue.main.async {
+ if self.lastPhotoURL == photoURL {
+ self.profileImage.image = image
+ }
+ }
+ }
+ }
+ } else {
+ lastPhotoURL = nil
+ self.profileImage.image = nil
+ }
+ updateControls()
+ }
+
+ // Updates the states of the UI controls.
+ fileprivate func updateControls() {
+ let action: Action
+ switch actionType {
+ case .auth:
+ action = authAction
+ case .user:
+ action = userAction
+ }
+ let isCredentialEnabled = action.requiresCredential
+ credentialTypePicker.isUserInteractionEnabled = isCredentialEnabled
+ credentialTypePicker.alpha = isCredentialEnabled ? 1.0 : 0.6
+ let isEmailEnabled = isCredentialEnabled && credentialType.requiresEmail || action.requiresEmail
+ emailInputLabel.alpha = isEmailEnabled ? 1.0 : 0.6
+ emailField.isEnabled = isEmailEnabled
+ let isPasswordEnabled = isCredentialEnabled && credentialType.requiresPassword ||
+ action.requiresPassword
+ passwordInputLabel.alpha = isPasswordEnabled ? 1.0 : 0.6
+ passwordField.isEnabled = isPasswordEnabled
+ }
+
+ fileprivate func showAlert(title: String, message: String? = "") {
+ UIAlertView(title: title, message: message ?? "(NULL)", delegate: nil, cancelButtonTitle: nil,
+ otherButtonTitles: "OK").show()
+ }
+
+ private func ifNoError(_ error: Error?, execute: () -> Void) {
+ guard error == nil else {
+ showAlert(title: "Error", message: error!.localizedDescription)
+ return
+ }
+ execute()
+ }
+}
+
+extension ViewController : GIDSignInUIDelegate {
+ func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
+ present(viewController, animated: true, completion: nil)
+ }
+
+ func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
+ dismiss(animated: true, completion: nil)
+ }
+}
+
+extension ViewController : UIPickerViewDataSource {
+ func numberOfComponents(in pickerView: UIPickerView) -> Int {
+ return 1
+ }
+
+ func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+ switch pickerView {
+ case actionTypePicker:
+ if Auth.auth().currentUser != nil {
+ return ActionType.countWithUser
+ } else {
+ return ActionType.countWithoutUser
+ }
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ return AuthAction.count
+ case .user:
+ return UserAction.count
+ }
+ case credentialTypePicker:
+ return CredentialType.count
+ default:
+ return 0
+ }
+ }
+}
+
+extension ViewController : UIPickerViewDelegate {
+ func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)
+ -> String? {
+ switch pickerView {
+ case actionTypePicker:
+ return ActionType(rawValue: row)!.text
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ return AuthAction(rawValue: row)!.text
+ case .user:
+ return UserAction(rawValue: row)!.text
+ }
+ case credentialTypePicker:
+ return CredentialType(rawValue: row)!.text
+ default:
+ return nil
+ }
+ }
+
+ func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+ switch pickerView {
+ case actionTypePicker:
+ actionType = ActionType(rawValue: row)!
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ authAction = AuthAction(rawValue: row)!
+ case .user:
+ userAction = UserAction(rawValue: row)!
+ }
+ case credentialTypePicker:
+ credentialType = CredentialType(rawValue: row)!
+ default:
+ break
+ }
+ updateControls()
+ }
+}
+
+/// An adapter class to pass GoogleSignIn delegate method to a block.
+fileprivate final class GoogleSignInDelegate: NSObject, GIDSignInDelegate {
+
+ private let completion: (GIDGoogleUser?, Error?) -> Void
+ private var retainedSelf: GoogleSignInDelegate?
+
+ init(completion: @escaping (GIDGoogleUser?, Error?) -> Void) {
+ self.completion = completion
+ super.init()
+ retainedSelf = self
+ }
+
+ func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser?, withError error: Error?) {
+ completion(user, error)
+ retainedSelf = nil
+ }
+}
+
+/// The list of all possible action types.
+fileprivate enum ActionType: Int {
+
+ case auth, user
+
+ // Count of action types when no user is signed in.
+ static var countWithoutUser: Int {
+ return ActionType.auth.rawValue + 1
+ }
+
+ // Count of action types when a user is signed in.
+ static var countWithUser: Int {
+ return ActionType.user.rawValue + 1
+ }
+
+ /// The text description for a particular enum value.
+ var text : String {
+ switch self {
+ case .auth:
+ return "Auth"
+ case .user:
+ return "User"
+ }
+ }
+}
+
+fileprivate protocol Action {
+ /// The text description for the particular action.
+ var text: String { get }
+
+ /// Whether or not the action requires credential.
+ var requiresCredential : Bool { get }
+
+ /// Whether or not the action requires email.
+ var requiresEmail: Bool { get }
+
+ /// Whether or not the credential requires password.
+ var requiresPassword: Bool { get }
+}
+
+/// The list of all possible actions the operator can take on the Auth object.
+fileprivate enum AuthAction: Int, Action {
+
+ case fetchProviderForEmail, signInAnonymously, signInWithCredential, createUser, signOut
+
+ /// Total number of auth actions.
+ static var count: Int {
+ return AuthAction.signOut.rawValue + 1
+ }
+
+ var text : String {
+ switch self {
+ case .fetchProviderForEmail:
+ return "Fetch Provider ⬇️"
+ case .signInAnonymously:
+ return "Sign In Anonymously"
+ case .signInWithCredential:
+ return "Sign In w/ Credential ↙️"
+ case .createUser:
+ return "Create User ⬇️"
+ case .signOut:
+ return "Sign Out"
+ }
+ }
+
+ var requiresCredential : Bool {
+ return self == .signInWithCredential
+ }
+
+ var requiresEmail : Bool {
+ return self == .fetchProviderForEmail || self == .createUser
+ }
+
+ var requiresPassword : Bool {
+ return self == .createUser
+ }
+}
+
+/// The list of all possible actions the operator can take on the User object.
+fileprivate enum UserAction: Int, Action {
+
+ case updateEmail, updatePassword, reload, reauthenticate, getToken, linkWithCredential,
+ deleteAccount
+
+ /// Total number of user actions.
+ static var count: Int {
+ return UserAction.deleteAccount.rawValue + 1
+ }
+
+ var text : String {
+ switch self {
+ case .updateEmail:
+ return "Update Email ⬇️"
+ case .updatePassword:
+ return "Update Password ⬇️"
+ case .reload:
+ return "Reload"
+ case .reauthenticate:
+ return "Reauthenticate ↙️"
+ case .getToken:
+ return "Get Token"
+ case .linkWithCredential:
+ return "Link With Credential ↙️"
+ case .deleteAccount:
+ return "Delete Account"
+ }
+ }
+
+ var requiresCredential : Bool {
+ return self == .reauthenticate || self == .linkWithCredential
+ }
+
+ var requiresEmail : Bool {
+ return self == .updateEmail
+ }
+
+ var requiresPassword : Bool {
+ return self == .updatePassword
+ }
+}
+
+/// The list of all possible credential types the operator can use to sign in or link.
+fileprivate enum CredentialType: Int {
+
+ case google, password
+
+ /// Total number of enum values.
+ static var count: Int {
+ return CredentialType.password.rawValue + 1
+ }
+
+ /// The text description for a particular enum value.
+ var text : String {
+ switch self {
+ case .google:
+ return "Google"
+ case .password:
+ return "Password ➡️️"
+ }
+ }
+
+ /// Whether or not the credential requires email.
+ var requiresEmail : Bool {
+ return self == .password
+ }
+
+ /// Whether or not the credential requires password.
+ var requiresPassword : Bool {
+ return self == .password
+ }
+}
+
+fileprivate extension User {
+ var textDescription: String {
+ return self.displayName ?? self.email ?? self.uid
+ }
+}