MainViewController.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <objc/runtime.h>
  17. #import <FirebaseCore/FIRApp.h>
  18. #import <FirebaseCore/FIRAppInternal.h>
  19. #import "AppManager.h"
  20. #import "AuthCredentials.h"
  21. #import "FacebookAuthProvider.h"
  22. #import "FirebaseAuth.h"
  23. #import "GoogleAuthProvider.h"
  24. #import "MainViewController+App.h"
  25. #import "MainViewController+Auth.h"
  26. #import "MainViewController+AutoTests.h"
  27. #import "MainViewController+Custom.h"
  28. #import "MainViewController+Email.h"
  29. #import "MainViewController+Facebook.h"
  30. #import "MainViewController+GameCenter.h"
  31. #import "MainViewController+Google.h"
  32. #import "MainViewController+Internal.h"
  33. #import "MainViewController+MultiFactor.h"
  34. #import "MainViewController+OAuth.h"
  35. #import "MainViewController+OOB.h"
  36. #import "MainViewController+Phone.h"
  37. #import "MainViewController+User.h"
  38. #import "SettingsViewController.h"
  39. #import "StaticContentTableViewManager.h"
  40. #import "UIViewController+Alerts.h"
  41. #import "UserInfoViewController.h"
  42. #import "UserTableViewCell.h"
  43. NS_ASSUME_NONNULL_BEGIN
  44. static NSString *const kSectionTitleSettings = @"Settings";
  45. static NSString *const kSectionTitleUserDetails = @"User Defaults";
  46. static NSString *const kSwitchToInMemoryUserTitle = @"Switch to in memory user";
  47. static NSString *const kNewOrExistingUserToggleTitle = @"New or Existing User Toggle";
  48. extern NSString *const FIRAuthErrorUserInfoMultiFactorResolverKey;
  49. typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error);
  50. @implementation MainViewController {
  51. NSMutableString *_consoleString;
  52. /** @var _userInMemory
  53. @brief Acts like the "memory" function of a calculator. An operation allows sample app users
  54. to assign this value based on @c FIRAuth.currentUser or clear this value.
  55. */
  56. FIRUser *_userInMemory;
  57. /** @var _useUserInMemory
  58. @brief Instructs the application to use _userInMemory instead of @c FIRAuth.currentUser for
  59. testing operations. This allows us to test if things still work with a user who is not
  60. the @c FIRAuth.currentUser, and also allows us to test those things while
  61. @c FIRAuth.currentUser remains nil (after a sign-out) and also when @c FIRAuth.currentUser
  62. is non-nil (do to a subsequent sign-in.)
  63. */
  64. BOOL _useUserInMemory;
  65. }
  66. - (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil {
  67. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  68. if (self) {
  69. _actionCodeRequestType = ActionCodeRequestTypeInApp;
  70. _actionCodeContinueURL = [NSURL URLWithString:KCONTINUE_URL];
  71. _authStateDidChangeListeners = [NSMutableArray array];
  72. _IDTokenDidChangeListeners = [NSMutableArray array];
  73. _googleOAuthProvider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID];
  74. _microsoftOAuthProvider = [FIROAuthProvider providerWithProviderID:@"microsoft.com"];
  75. _twitterOAuthProvider = [FIROAuthProvider providerWithProviderID:@"twitter.com"];
  76. _linkedinOAuthProvider = [FIROAuthProvider providerWithProviderID:@"linkedin.com"];
  77. _yahooOAuthProvider = [FIROAuthProvider providerWithProviderID:@"yahoo.com"];
  78. _gitHubOAuthProvider = [FIROAuthProvider providerWithProviderID:@"github.com"];
  79. [[NSNotificationCenter defaultCenter] addObserver:self
  80. selector:@selector(authStateChangedForAuth:)
  81. name:FIRAuthStateDidChangeNotification
  82. object:nil];
  83. self.useStatusBarSpinner = YES;
  84. }
  85. return self;
  86. }
  87. - (void)viewDidLoad {
  88. [super viewDidLoad];
  89. // Give us a circle for the image view:
  90. _userInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
  91. _userInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
  92. _userInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
  93. _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
  94. _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
  95. _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
  96. }
  97. - (void)viewWillAppear:(BOOL)animated {
  98. [super viewWillAppear:animated];
  99. [self updateTable];
  100. [self updateUserInfo];
  101. }
  102. #pragma mark - Public
  103. - (BOOL)handleIncomingLinkWithURL:(NSURL *)URL {
  104. // Parse the query portion of the incoming URL.
  105. NSDictionary<NSString *, NSString *> *queryItems =
  106. parseURL([NSURLComponents componentsWithString:URL.absoluteString].query);
  107. // Check that all necessary query items are available.
  108. NSString *actionCode = queryItems[@"oobCode"];
  109. NSString *mode = queryItems[@"mode"];
  110. if (!actionCode || !mode) {
  111. return NO;
  112. }
  113. // Handle Password Reset action.
  114. if ([mode isEqualToString:kPasswordResetAction]) {
  115. [self showTextInputPromptWithMessage:@"New Password:"
  116. completionBlock:^(BOOL userPressedOK, NSString *_Nullable newPassword) {
  117. if (!userPressedOK || !newPassword.length) {
  118. [UIPasteboard generalPasteboard].string = actionCode;
  119. return;
  120. }
  121. [self showSpinner:^() {
  122. [[AppManager auth] confirmPasswordResetWithCode:actionCode
  123. newPassword:newPassword
  124. completion:^(NSError *_Nullable error) {
  125. [self hideSpinner:^{
  126. if (error) {
  127. [self logFailure:@"Password reset in app failed" error:error];
  128. [self showMessagePrompt:error.localizedDescription];
  129. return;
  130. }
  131. [self logSuccess:@"Password reset in app succeeded."];
  132. [self showMessagePrompt:@"Password reset in app succeeded."];
  133. }];
  134. }];
  135. }];
  136. }];
  137. return YES;
  138. }
  139. if ([mode isEqualToString:kVerifyEmailAction]) {
  140. [self showMessagePromptWithTitle:@"Tap OK to verify email"
  141. message:actionCode
  142. showCancelButton:YES
  143. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  144. if (!userPressedOK) {
  145. return;
  146. }
  147. [self showSpinner:^() {
  148. [[AppManager auth] applyActionCode:actionCode completion:^(NSError *_Nullable error) {
  149. [self hideSpinner:^{
  150. if (error) {
  151. [self logFailure:@"Verify email in app failed" error:error];
  152. [self showMessagePrompt:error.localizedDescription];
  153. return;
  154. }
  155. [self logSuccess:@"Verify email in app succeeded."];
  156. [self showMessagePrompt:@"Verify email in app succeeded."];
  157. }];
  158. }];
  159. }];
  160. }];
  161. return YES;
  162. }
  163. return NO;
  164. }
  165. static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) {
  166. NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
  167. NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
  168. NSMutableDictionary<NSString *, NSString *> *queryItems =
  169. [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
  170. for (NSString *component in URLComponents) {
  171. NSRange equalRange = [component rangeOfString:@"="];
  172. if (equalRange.location != NSNotFound) {
  173. NSString *queryItemKey =
  174. [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
  175. NSString *queryItemValue =
  176. [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
  177. if (queryItemKey && queryItemValue) {
  178. queryItems[queryItemKey] = queryItemValue;
  179. }
  180. }
  181. }
  182. return queryItems;
  183. }
  184. - (void)updateTable {
  185. __weak typeof(self) weakSelf = self;
  186. _tableViewManager.contents =
  187. [StaticContentTableViewContent contentWithSections:@[
  188. // User Defaults
  189. [StaticContentTableViewSection sectionWithTitle:kSectionTitleUserDetails cells:@[
  190. [StaticContentTableViewCell cellWithCustomCell:_userInfoTableViewCell action:^{
  191. [weakSelf presentUserInfo];
  192. }],
  193. [StaticContentTableViewCell cellWithCustomCell:_userToUseCell],
  194. [StaticContentTableViewCell cellWithCustomCell:_userInMemoryInfoTableViewCell action:^{
  195. [weakSelf presentUserInMemoryInfo];
  196. }],
  197. ]],
  198. // Settings
  199. [StaticContentTableViewSection sectionWithTitle:kSectionTitleSettings cells:@[
  200. [StaticContentTableViewCell cellWithTitle:kSectionTitleSettings
  201. action:^{ [weakSelf presentSettings]; }],
  202. [StaticContentTableViewCell cellWithTitle:kNewOrExistingUserToggleTitle
  203. value:_isNewUserToggleOn ? @"Enabled" : @"Disabled"
  204. action:^{
  205. _isNewUserToggleOn = !_isNewUserToggleOn;
  206. [self updateTable]; }],
  207. [StaticContentTableViewCell cellWithTitle:kSwitchToInMemoryUserTitle
  208. action:^{ [weakSelf updateToSavedUser]; }],
  209. ]],
  210. // Auth
  211. [weakSelf authSection],
  212. // User
  213. [weakSelf userSection],
  214. // Multi Factor
  215. [weakSelf multiFactorSection],
  216. // Email Auth
  217. [weakSelf emailAuthSection],
  218. // Phone Auth
  219. [weakSelf phoneAuthSection],
  220. // Google Auth
  221. [weakSelf googleAuthSection],
  222. // Facebook Auth
  223. [weakSelf facebookAuthSection],
  224. // OAuth
  225. [weakSelf oAuthSection],
  226. // Custom Auth
  227. [weakSelf customAuthSection],
  228. // Game Center Auth
  229. [weakSelf gameCenterAuthSection],
  230. // App
  231. [weakSelf appSection],
  232. // OOB
  233. [weakSelf oobSection],
  234. // Auto Tests
  235. [weakSelf autoTestsSection],
  236. ]];
  237. }
  238. #pragma mark - Internal
  239. - (FIRUser *)user {
  240. return _useUserInMemory ? _userInMemory : [AppManager auth].currentUser;
  241. }
  242. - (void)signInWithProvider:(nonnull id<AuthProvider>)provider callback:(void(^)(void))callback {
  243. if (!provider) {
  244. [self logFailedTest:@"A valid auth provider was not provided to the signInWithProvider."];
  245. return;
  246. }
  247. [provider getAuthCredentialWithPresentingViewController:self
  248. callback:^(FIRAuthCredential *credential,
  249. NSError *error) {
  250. if (!credential) {
  251. [self logFailedTest:@"The test needs a valid credential to continue."];
  252. return;
  253. }
  254. [[AppManager auth] signInWithCredential:credential
  255. completion:^(FIRAuthDataResult *_Nullable result,
  256. NSError *_Nullable error) {
  257. if (error) {
  258. [self logFailure:@"sign-in with provider failed" error:error];
  259. [self logFailedTest:@"Sign-in should succeed"];
  260. return;
  261. } else {
  262. [self logSuccess:@"sign-in with provider succeeded."];
  263. callback();
  264. }
  265. }];
  266. }];
  267. }
  268. - (FIRActionCodeSettings *)actionCodeSettings {
  269. FIRActionCodeSettings *actionCodeSettings = [[FIRActionCodeSettings alloc] init];
  270. actionCodeSettings.URL = self.actionCodeContinueURL;
  271. actionCodeSettings.handleCodeInApp = self.actionCodeRequestType == ActionCodeRequestTypeInApp;
  272. return actionCodeSettings;
  273. }
  274. - (void)reauthenticate:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
  275. FIRUser *user = [self user];
  276. if (!user) {
  277. NSString *provider = @"Firebase";
  278. if ([authProvider isKindOfClass:[GoogleAuthProvider class]]) {
  279. provider = @"Google";
  280. } else if ([authProvider isKindOfClass:[FacebookAuthProvider class]]) {
  281. provider = @"Facebook";
  282. }
  283. NSString *title = @"Missing User";
  284. NSString *message =
  285. [NSString stringWithFormat:@"There is no signed-in %@ user.", provider];
  286. [self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil];
  287. return;
  288. }
  289. [authProvider getAuthCredentialWithPresentingViewController:self
  290. callback:^(FIRAuthCredential *credential,
  291. NSError *error) {
  292. if (credential) {
  293. FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
  294. NSError *_Nullable error) {
  295. if (error) {
  296. if (error.code == FIRAuthErrorCodeSecondFactorRequired) {
  297. FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
  298. NSMutableString *displayNameString = [NSMutableString string];
  299. for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
  300. [displayNameString appendString:tmpFactorInfo.displayName];
  301. [displayNameString appendString:@" "];
  302. }
  303. [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to reauthenticate\n%@", displayNameString]
  304. completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) {
  305. FIRPhoneMultiFactorInfo* selectedHint;
  306. for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
  307. if ([displayName isEqualToString:tmpFactorInfo.displayName]) {
  308. selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo;
  309. }
  310. }
  311. [FIRPhoneAuthProvider.provider
  312. verifyPhoneNumberWithMultiFactorInfo:selectedHint
  313. UIDelegate:nil
  314. multiFactorSession:resolver.session
  315. completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
  316. if (error) {
  317. [self logFailure:@"Multi factor start sign in failed." error:error];
  318. } else {
  319. [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName]
  320. completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) {
  321. FIRPhoneAuthCredential *credential =
  322. [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
  323. verificationCode:verificationCode];
  324. FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
  325. [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  326. if (error) {
  327. [self logFailure:@"Multi factor finalize sign in failed." error:error];
  328. } else {
  329. [self logSuccess:@"Multi factor finalize sign in succeeded."];
  330. }
  331. }];
  332. }];
  333. }
  334. }];
  335. }];
  336. } else {
  337. [self logFailure:@"reauthenticate operation failed." error:error];
  338. }
  339. } else {
  340. [self logSuccess:@"reauthenticate operation succeeded."];
  341. }
  342. if (authResult.additionalUserInfo) {
  343. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  344. }
  345. };
  346. [user reauthenticateWithCredential:credential completion:completion];
  347. }
  348. }];
  349. }
  350. - (void)signinWithProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
  351. FIRAuth *auth = [AppManager auth];
  352. if (!auth) {
  353. return;
  354. }
  355. [authProvider getAuthCredentialWithPresentingViewController:self
  356. callback:^(FIRAuthCredential *credential,
  357. NSError *error) {
  358. if (credential) {
  359. FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
  360. NSError *_Nullable error) {
  361. if (error) {
  362. [self logFailure:@"sign-in with provider failed" error:error];
  363. } else {
  364. [self logSuccess:@"sign-in with provider succeeded."];
  365. }
  366. if (authResult.additionalUserInfo) {
  367. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  368. if (_isNewUserToggleOn) {
  369. NSString *newUserString = authResult.additionalUserInfo.isNewUser ?
  370. @"New user" : @"Existing user";
  371. [self showMessagePromptWithTitle:@"New or Existing"
  372. message:newUserString
  373. showCancelButton:NO
  374. completion:nil];
  375. }
  376. }
  377. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
  378. };
  379. [auth signInWithCredential:credential completion:completion];
  380. }
  381. }];
  382. }
  383. - (void)linkWithAuthProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
  384. FIRUser *user = [self user];
  385. if (!user) {
  386. return;
  387. }
  388. [authProvider getAuthCredentialWithPresentingViewController:self
  389. callback:^(FIRAuthCredential *credential,
  390. NSError *error) {
  391. if (credential) {
  392. FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
  393. NSError *_Nullable error) {
  394. if (error) {
  395. if (error.code == FIRAuthErrorCodeSecondFactorRequired) {
  396. FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
  397. NSMutableString *displayNameString = [NSMutableString string];
  398. for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
  399. [displayNameString appendString:tmpFactorInfo.displayName];
  400. [displayNameString appendString:@" "];
  401. }
  402. [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to link\n%@", displayNameString]
  403. completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) {
  404. FIRPhoneMultiFactorInfo* selectedHint;
  405. for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
  406. if ([displayName isEqualToString:tmpFactorInfo.displayName]) {
  407. selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo;
  408. }
  409. }
  410. [FIRPhoneAuthProvider.provider
  411. verifyPhoneNumberWithMultiFactorInfo:selectedHint
  412. UIDelegate:nil
  413. multiFactorSession:resolver.session
  414. completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
  415. if (error) {
  416. [self logFailure:@"Multi factor start sign in failed." error:error];
  417. } else {
  418. [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName]
  419. completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) {
  420. FIRPhoneAuthCredential *credential =
  421. [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
  422. verificationCode:verificationCode];
  423. FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
  424. [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  425. if (error) {
  426. [self logFailure:@"Multi factor finalize sign in failed." error:error];
  427. } else {
  428. [self logSuccess:@"Multi factor finalize sign in succeeded."];
  429. }
  430. }];
  431. }];
  432. }
  433. }];
  434. }];
  435. } else {
  436. [self logFailure:@"link auth provider failed" error:error];
  437. }
  438. } else {
  439. [self logSuccess:@"link auth provider succeeded."];
  440. }
  441. if (authResult.additionalUserInfo) {
  442. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  443. }
  444. };
  445. [user linkWithCredential:credential completion:completion];
  446. }
  447. }];
  448. }
  449. - (void)unlinkFromProvider:(NSString *)provider
  450. completion:(nullable TestAutomationCallback)completion {
  451. [[self user] unlinkFromProvider:provider
  452. completion:^(FIRUser *_Nullable user,
  453. NSError *_Nullable error) {
  454. if (error) {
  455. [self logFailure:@"unlink auth provider failed" error:error];
  456. if (completion) {
  457. completion(error);
  458. }
  459. return;
  460. }
  461. [self logSuccess:@"unlink auth provider succeeded."];
  462. if (completion) {
  463. completion(nil);
  464. }
  465. [self showTypicalUIForUserUpdateResultsWithTitle:@"Unlink from Provider" error:error];
  466. }];
  467. }
  468. - (void)updateToSavedUser {
  469. if(![AppManager auth].currentUser) {
  470. NSLog(@"You must be signed in to perform this action");
  471. return;
  472. }
  473. if (!_userInMemory) {
  474. [self showMessagePrompt:[NSString stringWithFormat:@"You need an in-memory user to perform this"
  475. "action, use the M+ button to save a user to memory.", nil]];
  476. return;
  477. }
  478. [[AppManager auth] updateCurrentUser:_userInMemory completion:^(NSError *_Nullable error) {
  479. if (error) {
  480. [self showMessagePrompt:
  481. [NSString stringWithFormat:@"An error Occurred: %@", error.localizedDescription]];
  482. return;
  483. }
  484. }];
  485. }
  486. #pragma mark - Private
  487. - (void)presentSettings {
  488. SettingsViewController *settingsViewController = [[SettingsViewController alloc]
  489. initWithNibName:NSStringFromClass([SettingsViewController class])
  490. bundle:nil];
  491. [self showViewController:settingsViewController sender:self];
  492. }
  493. - (void)presentUserInfo {
  494. UserInfoViewController *userInfoViewController =
  495. [[UserInfoViewController alloc] initWithUser:[AppManager auth].currentUser];
  496. [self showViewController:userInfoViewController sender:self];
  497. }
  498. - (void)presentUserInMemoryInfo {
  499. UserInfoViewController *userInfoViewController =
  500. [[UserInfoViewController alloc] initWithUser:_userInMemory];
  501. [self showViewController:userInfoViewController sender:self];
  502. }
  503. - (NSString *)stringWithAdditionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo {
  504. if (!additionalUserInfo) {
  505. return @"(no additional user info)";
  506. }
  507. NSString *newUserString = additionalUserInfo.isNewUser ? @"new user" : @"existing user";
  508. return [NSString stringWithFormat:@"%@: %@", newUserString, additionalUserInfo.profile];
  509. }
  510. - (void)showTypicalUIForUserUpdateResultsWithTitle:(NSString *)resultsTitle
  511. error:(NSError * _Nullable)error {
  512. if (error) {
  513. NSString *message = [NSString stringWithFormat:@"%@ (%ld)\n%@",
  514. error.domain,
  515. (long)error.code,
  516. error.localizedDescription];
  517. if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
  518. NSString *errorEmail = error.userInfo[FIRAuthErrorUserInfoEmailKey];
  519. resultsTitle = [NSString stringWithFormat:@"Existing email : %@", errorEmail];
  520. }
  521. [self showMessagePromptWithTitle:resultsTitle
  522. message:message
  523. showCancelButton:NO
  524. completion:nil];
  525. return;
  526. }
  527. [self updateUserInfo];
  528. }
  529. - (void)showUIForAuthDataResultWithResult:(FIRAuthDataResult *)result
  530. error:(NSError * _Nullable)error {
  531. NSString *errorMessage = [NSString stringWithFormat:@"%@ (%ld)\n%@",
  532. error.domain ?: @"",
  533. (long)error.code,
  534. error.localizedDescription ?: @""];
  535. [self showMessagePromptWithTitle:@"Error"
  536. message:errorMessage
  537. showCancelButton:NO
  538. completion:^(BOOL userPressedOK,
  539. NSString *_Nullable userInput) {
  540. [self showMessagePromptWithTitle:@"Profile Info"
  541. message:[self stringWithAdditionalUserInfo:result.additionalUserInfo]
  542. showCancelButton:NO
  543. completion:nil];
  544. [self updateUserInfo];
  545. }];
  546. }
  547. - (void)updateUserInfo {
  548. [_userInfoTableViewCell updateContentsWithUser:[AppManager auth].currentUser];
  549. [_userInMemoryInfoTableViewCell updateContentsWithUser:_userInMemory];
  550. }
  551. - (void)authStateChangedForAuth:(NSNotification *)notification {
  552. [self updateUserInfo];
  553. if (notification) {
  554. [self log:[NSString stringWithFormat:
  555. @"received FIRAuthStateDidChange notification on user '%@'.",
  556. ((FIRAuth *)notification.object).currentUser.uid]];
  557. }
  558. }
  559. - (void)log:(NSString *)string {
  560. dispatch_async(dispatch_get_main_queue(), ^{
  561. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  562. dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
  563. NSString *date = [dateFormatter stringFromDate:[NSDate date]];
  564. if (!_consoleString) {
  565. _consoleString = [NSMutableString string];
  566. }
  567. [_consoleString appendString:[NSString stringWithFormat:@"%@ %@\n", date, string]];
  568. _consoleTextView.text = _consoleString;
  569. CGRect targetRect = CGRectMake(0, _consoleTextView.contentSize.height - 1, 1, 1);
  570. [_consoleTextView scrollRectToVisible:targetRect animated:YES];
  571. });
  572. }
  573. - (void)logSuccess:(NSString *)string {
  574. [self log:[NSString stringWithFormat:@"SUCCESS: %@", string]];
  575. }
  576. - (void)logFailure:(NSString *)string error:(NSError * _Nullable) error {
  577. NSString *message =
  578. [NSString stringWithFormat:@"FAILURE: %@ Error Description: %@.", string, error.description];
  579. [self log:message];
  580. }
  581. - (void)logFailedTest:( NSString *_Nonnull )reason {
  582. [self log:[NSString stringWithFormat:@"FAILIURE: TEST FAILED - %@", reason]];
  583. }
  584. #pragma mark - IBAction
  585. - (IBAction)userToUseDidChange:(UISegmentedControl *)sender {
  586. _useUserInMemory = (sender.selectedSegmentIndex == 1);
  587. }
  588. - (IBAction)memoryPlus {
  589. _userInMemory = [AppManager auth].currentUser;
  590. [self updateUserInfo];
  591. }
  592. - (IBAction)memoryClear {
  593. _userInMemory = nil;
  594. [self updateUserInfo];
  595. }
  596. - (IBAction)clearConsole:(id)sender {
  597. [_consoleString appendString:@"\n\n"];
  598. _consoleTextView.text = @"";
  599. }
  600. - (IBAction)copyConsole:(id)sender {
  601. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  602. pasteboard.string = _consoleString ?: @"";
  603. }
  604. @end
  605. NS_ASSUME_NONNULL_END