MainViewController.m 28 KB

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