FBSDKLoginKit/FBSDKLoginKit/FBSDKLoginButton.m (365 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ #if !TARGET_OS_TV #import "FBSDKLoginButton.h" #import <FBSDKCoreKit_Basics/FBSDKCoreKit_Basics.h> #import <FBSDKLoginKit/FBSDKLoginKit-Swift.h> #import "FBSDKLoginAppEventName.h" #import "FBSDKLoginButtonDelegate.h" #import "FBSDKLoginManager+Internal.h" #import "FBSDKLoginTooltipView.h" #import "FBSDKNonceUtility.h" static const CGFloat kFBLogoSize = 16.0; static const CGFloat kFBLogoLeftMargin = 6.0; static const CGFloat kButtonHeight = 28.0; static const CGFloat kRightMargin = 8.0; static const CGFloat kPaddingBetweenLogoTitle = 8.0; @interface FBSDKLoginButton () @property (nonatomic) BOOL hasShownTooltipBubble; @property (nonatomic) NSString *userID; @property (nonatomic) NSString *userName; @property (nonatomic) id<_FBSDKUserInterfaceElementProviding> elementProvider; @property (nonatomic) id<_FBSDKUserInterfaceStringProviding> stringProvider; @property (nonatomic) id<FBSDKLoginProviding> loginProvider; @property (nonatomic) id<FBSDKGraphRequestFactory> graphRequestFactory; @end @implementation FBSDKLoginButton // MARK: - Type Dependencies - (void)configureWithElementProvider:(nonnull id<_FBSDKUserInterfaceElementProviding>)elementProvider stringProvider:(nonnull id<_FBSDKUserInterfaceStringProviding>)stringProvider loginProvider:(nonnull id<FBSDKLoginProviding>)loginProvider graphRequestFactory:(nonnull id<FBSDKGraphRequestFactory>)graphRequestFactory { self.elementProvider = elementProvider; self.stringProvider = stringProvider; self.loginProvider = loginProvider; self.graphRequestFactory = graphRequestFactory; } - (void)configureDefaultTypeDependencies { [self configureWithElementProvider:FBSDKInternalUtility.sharedUtility stringProvider:FBSDKInternalUtility.sharedUtility loginProvider:[FBSDKLoginManager new] graphRequestFactory:[FBSDKGraphRequestFactory new]]; } #pragma mark - Properties - (FBSDKDefaultAudience)defaultAudience { return self.loginProvider.defaultAudience; } - (void)setDefaultAudience:(FBSDKDefaultAudience)defaultAudience { self.loginProvider.defaultAudience = defaultAudience; } - (void)setLoginTracking:(FBSDKLoginTracking)loginTracking { _loginTracking = loginTracking; [self _updateNotificationObservers]; } - (void)setNonce:(NSString *)nonce { if ([FBSDKNonceUtility isValidNonce:nonce]) { _nonce = [nonce copy]; } else { _nonce = nil; NSString *msg = [NSString stringWithFormat:@"Unable to set invalid nonce: %@ on FBSDKLoginButton", nonce]; [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors logEntry:msg]; } } #pragma mark - UIView - (void)didMoveToWindow { [super didMoveToWindow]; if (self.window && ((self.tooltipBehavior == FBSDKLoginButtonTooltipBehaviorForceDisplay) || !_hasShownTooltipBubble)) { [self _showTooltipIfNeeded]; _hasShownTooltipBubble = YES; } } #pragma mark - Layout - (CGRect)imageRectForContentRect:(CGRect)contentRect { CGFloat centerY = CGRectGetMidY(contentRect); CGFloat y = centerY - (kFBLogoSize / 2.0); return CGRectMake(kFBLogoLeftMargin, y, kFBLogoSize, kFBLogoSize); } - (CGRect)titleRectForContentRect:(CGRect)contentRect { if (self.hidden || CGRectIsEmpty(self.bounds)) { return CGRectZero; } CGRect imageRect = [self imageRectForContentRect:contentRect]; CGFloat titleX = CGRectGetMaxX(imageRect) + kPaddingBetweenLogoTitle; CGRect titleRect = CGRectMake(titleX, 0, CGRectGetWidth(contentRect) - titleX - kRightMargin, CGRectGetHeight(contentRect)); return titleRect; } - (void)layoutSubviews { CGSize size = self.bounds.size; CGSize longTitleSize = [self sizeThatFits:size title:[self _longLogInTitle]]; NSString *title = (longTitleSize.width <= size.width ? [self _longLogInTitle] : [self _shortLogInTitle]); if (![title isEqualToString:[self titleForState:UIControlStateNormal]]) { [self setTitle:title forState:UIControlStateNormal]; } [super layoutSubviews]; } - (CGSize)sizeThatFits:(CGSize)size { if (self.hidden) { return CGSizeZero; } UIFont *font = self.titleLabel.font; CGSize selectedSize = [self textSizeForText:[self _logOutTitle] font:font constrainedSize:size lineBreakMode:self.titleLabel.lineBreakMode]; CGSize normalSize = [self textSizeForText:[self _longLogInTitle] font:font constrainedSize:size lineBreakMode:self.titleLabel.lineBreakMode]; if (normalSize.width > size.width) { normalSize = [self textSizeForText:[self _shortLogInTitle] font:font constrainedSize:size lineBreakMode:self.titleLabel.lineBreakMode]; } CGFloat titleWidth = MAX(normalSize.width, selectedSize.width); CGFloat buttonWidth = kFBLogoLeftMargin + kFBLogoSize + kPaddingBetweenLogoTitle + titleWidth + kRightMargin; return CGSizeMake(buttonWidth, kButtonHeight); } #pragma mark - FBSDKButton - (void)configureButton { [self configureDefaultTypeDependencies]; NSString *logInTitle = [self _shortLogInTitle]; NSString *logOutTitle = [self _logOutTitle]; [self configureWithIcon:nil title:logInTitle backgroundColor:self.backgroundColor highlightedColor:nil selectedTitle:logOutTitle selectedIcon:nil selectedColor:self.backgroundColor selectedHighlightedColor:nil]; self.titleLabel.textAlignment = NSTextAlignmentCenter; [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:kButtonHeight]]; [self _initializeContent]; [self addTarget:self action:@selector(_buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; [self _updateNotificationObservers]; self.authType = FBSDKLoginAuthTypeRerequest; self.codeVerifier = [FBSDKCodeVerifier new]; } - (void)_updateNotificationObservers { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_profileDidChangeNotification:) name:FBSDKProfileDidChangeNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_accessTokenDidChangeNotification:) name:FBSDKAccessTokenDidChangeNotification object:nil]; } - (void)_accessTokenDidChangeNotification:(NSNotification *)notification { if (notification.userInfo[FBSDKAccessTokenDidChangeUserIDKey] || notification.userInfo[FBSDKAccessTokenDidExpireKey]) { [self _updateContentForAccessToken]; } } - (void)_profileDidChangeNotification:(NSNotification *)notification { [self _updateContentForUserProfile:FBSDKProfile.currentProfile]; } - (void)_buttonPressed:(id)sender { if (self._isAuthenticated) { if (self.loginTracking != FBSDKLoginTrackingLimited) { [self logTapEventWithEventName:FBSDKAppEventNameFBSDKLoginButtonDidTap parameters:nil]; } NSString *title = nil; if (_userName) { NSString *localizedFormatString = NSLocalizedStringWithDefaultValue( @"LoginButton.LoggedInAs", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Logged in as %@", @"The format string for the FBSDKLoginButton label when the user is logged in" ); title = [NSString localizedStringWithFormat:localizedFormatString, _userName]; } else { NSString *localizedLoggedIn = NSLocalizedStringWithDefaultValue( @"LoginButton.LoggedIn", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Logged in using Facebook", @"The fallback string for the FBSDKLoginButton label when the user name is not available yet" ); title = localizedLoggedIn; } NSString *cancelTitle = NSLocalizedStringWithDefaultValue( @"LoginButton.CancelLogout", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Cancel", @"The label for the FBSDKLoginButton action sheet to cancel logging out" ); NSString *logOutTitle = NSLocalizedStringWithDefaultValue( @"LoginButton.ConfirmLogOut", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Log Out", @"The label for the FBSDKLoginButton action sheet to confirm logging out" ); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; alertController.popoverPresentationController.sourceView = self; alertController.popoverPresentationController.sourceRect = self.bounds; UIAlertAction *cancel = [UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:nil]; UIAlertAction *logout = [UIAlertAction actionWithTitle:logOutTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction *_Nonnull action) { [self _logout]; }]; [alertController addAction:cancel]; [alertController addAction:logout]; UIViewController *topMostViewController = [self.elementProvider topMostViewController]; [topMostViewController presentViewController:alertController animated:YES completion:nil]; } else { if ([self.delegate respondsToSelector:@selector(loginButtonWillLogin:)]) { if (![self.delegate loginButtonWillLogin:self]) { return; } } FBSDKLoginManagerLoginResultBlock handler = ^(FBSDKLoginManagerLoginResult *result, NSError *error) { if ([self.delegate respondsToSelector:@selector(loginButton:didCompleteWithResult:error:)]) { [self.delegate loginButton:self didCompleteWithResult:result error:error]; } }; FBSDKLoginConfiguration *loginConfig = [self loginConfiguration]; if (self.loginTracking == FBSDKLoginTrackingEnabled) { [self logTapEventWithEventName:FBSDKAppEventNameFBSDKLoginButtonDidTap parameters:nil]; } if (loginConfig != nil) { [self.loginProvider logInFromViewController:[self.elementProvider viewControllerForView:self] configuration:loginConfig completion:handler]; } } } - (FBSDKLoginConfiguration *)loginConfiguration { NSString *nonce = self.nonce ?: NSUUID.UUID.UUIDString; return [[FBSDKLoginConfiguration alloc] initWithPermissions:self.permissions tracking:self.loginTracking nonce:nonce messengerPageId:self.messengerPageId authType:self.authType codeVerifier:self.codeVerifier]; } - (NSString *)_logOutTitle { return NSLocalizedStringWithDefaultValue( @"LoginButton.LogOut", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Log out", @"The label for the FBSDKLoginButton when the user is currently logged in" ); } - (NSString *)_longLogInTitle { return NSLocalizedStringWithDefaultValue( @"LoginButton.LogInContinue", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Continue with Facebook", @"The long label for the FBSDKLoginButton when the user is currently logged out" ); } - (NSString *)_shortLogInTitle { return NSLocalizedStringWithDefaultValue( @"LoginButton.LogIn", @"FacebookSDK", [self.stringProvider bundleForStrings], @"Log in", @"The short label for the FBSDKLoginButton when the user is currently logged out" ); } - (void)_showTooltipIfNeeded { if (self._isAuthenticated || self.tooltipBehavior == FBSDKLoginButtonTooltipBehaviorDisable) { return; } else { FBSDKLoginTooltipView *tooltipView = [FBSDKLoginTooltipView new]; tooltipView.colorStyle = self.tooltipColorStyle; if (self.tooltipBehavior == FBSDKLoginButtonTooltipBehaviorForceDisplay) { tooltipView.forceDisplay = YES; } [tooltipView presentFromView:self]; } } // On initial setting of button state. We want to update the button's user // information using the most comprehensive available. // If access token is available use that. // If only profile is available, use that. - (void)_initializeContent { FBSDKAccessToken *accessToken = FBSDKAccessToken.currentAccessToken; FBSDKProfile *profile = FBSDKProfile.currentProfile; if (accessToken) { [self _updateContentForAccessToken]; } else if (profile) { [self _updateContentForUserProfile:profile]; } else { self.selected = NO; } } - (void)_updateContentForAccessToken { BOOL accessTokenIsValid = FBSDKAccessToken.isCurrentAccessTokenActive; self.selected = accessTokenIsValid; if (accessTokenIsValid) { if (![FBSDKAccessToken.currentAccessToken.userID isEqualToString:_userID]) { [self _fetchAndSetContent]; } } } - (void)_fetchAndSetContent { id<FBSDKGraphRequest> request = [[self graphRequestFactory] createGraphRequestWithGraphPath:@"me" parameters:@{@"fields" : @"id,name"} flags:FBSDKGraphRequestFlagDisableErrorRecovery]; [request startWithCompletion:^(id<FBSDKGraphRequestConnecting> connection, id result, NSError *error) { NSString *userID = [FBSDKTypeUtility dictionary:result objectForKey:@"id" ofType:NSString.class]; if (!error && [FBSDKAccessToken.currentAccessToken.userID isEqualToString:userID]) { self->_userName = [FBSDKTypeUtility dictionary:result objectForKey:@"name" ofType:NSString.class]; self->_userID = userID; } }]; } - (void)_updateContentForUserProfile:(nullable FBSDKProfile *)profile { self.selected = profile != nil; if (profile && [self _userInformationDoesNotMatchProfile:profile]) { _userName = profile.name; _userID = profile.userID; } } - (BOOL)_userInformationDoesNotMatchProfile:(FBSDKProfile *)profile { return (profile.userID != _userID) || (profile.name != _userName); } - (BOOL)_isAuthenticated { return (FBSDKAccessToken.currentAccessToken || FBSDKAuthenticationToken.currentAuthenticationToken); } - (void)_logout { [self.loginProvider logOut]; [self.delegate loginButtonDidLogOut:self]; } @end #endif