diff options
author | 2017-05-15 12:27:07 -0700 | |
---|---|---|
committer | 2017-05-15 12:27:07 -0700 | |
commit | 98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch) | |
tree | 131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /AuthSamples | |
parent | 32461366c9e204a527ca05e6e9b9404a2454ac51 (diff) |
Initial
Diffstat (limited to 'AuthSamples')
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 Binary files differnew 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 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 Binary files differnew file mode 100644 index 0000000..2976035 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_21in29dp.png 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 Binary files differnew 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 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 Binary files differnew file mode 100644 index 0000000..0c98554 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_38in50dp.png 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 Binary files differnew file mode 100644 index 0000000..3ef403a --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_42in57dp.png 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 Binary files differnew file mode 100644 index 0000000..ff6c804 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_53in72dp.png 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 Binary files differnew file mode 100644 index 0000000..df8a953 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_1x_ios_56in76dp.png 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 Binary files differnew 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 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 Binary files differnew file mode 100644 index 0000000..4067017 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_21in29dp.png 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 Binary files differnew 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 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 Binary files differnew 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 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 Binary files differnew file mode 100644 index 0000000..436be10 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_38in50dp.png 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 Binary files differnew file mode 100644 index 0000000..e9c869e --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_42in57dp.png 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 Binary files differnew file mode 100644 index 0000000..8c5ce9d --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_44in60dp.png 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 Binary files differnew file mode 100644 index 0000000..0ddd720 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_53in72dp.png 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 Binary files differnew file mode 100644 index 0000000..2f028cb --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_2x_ios_56in76dp.png 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 Binary files differnew file mode 100644 index 0000000..69bb8d3 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_21in29dp.png 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 Binary files differnew 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 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 Binary files differnew file mode 100644 index 0000000..211ef93 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/AppIcon.appiconset/logo_avatar_square_grey_color_3x_ios_44in60dp.png 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 Binary files differnew file mode 100644 index 0000000..40a1a84 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_1x_ios_24dp.png 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 Binary files differnew file mode 100644 index 0000000..6bc4372 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_2x_ios_24dp.png 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 Binary files differnew file mode 100644 index 0000000..51b4401 --- /dev/null +++ b/AuthSamples/Sample/Images.xcassets/close.imageset/ic_clear_black_3x_ios_24dp.png 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 + } +} |