| // Copyright 2019 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:cached_network_image/cached_network_image.dart'; |
| import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart' |
| show ImageRenderMethodForWeb; |
| import 'package:firebase_auth/firebase_auth.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:provider/provider.dart'; |
| |
| import '../service/firebase_auth.dart'; |
| import 'sign_in_button/sign_in_button.dart'; |
| |
| enum _SignInButtonAction { |
| logout, |
| linkGithub, |
| unlinkGithub, |
| linkGoogle, |
| unlinkGoogle, |
| } |
| |
| /// Widget for displaying sign in information for the current user. |
| /// |
| /// If logged in, it will display the user's avatar. Clicking it opens a dropdown for logging out. |
| /// Otherwise, a sign in button will show. |
| class UserSignIn extends StatelessWidget { |
| const UserSignIn({super.key}); |
| |
| @override |
| Widget build(BuildContext context) { |
| final authService = Provider.of<FirebaseAuthService>(context); |
| |
| // Listen to the changes of `authService` to re-render. |
| return AnimatedBuilder( |
| animation: authService, |
| builder: (BuildContext context, _) { |
| if (authService.user != null) { |
| return PopupMenuButton<_SignInButtonAction>( |
| offset: const Offset(0, 50), |
| itemBuilder: (BuildContext context) => |
| _buildLinkUnlinkMenuItem(authService.user!.providerData), |
| onSelected: (_SignInButtonAction value) async { |
| switch (value) { |
| case _SignInButtonAction.logout: |
| await authService.signOut(); |
| break; |
| case _SignInButtonAction.linkGithub: |
| await authService.linkWithGithub(); |
| break; |
| case _SignInButtonAction.unlinkGithub: |
| await authService.unlinkGithub(); |
| break; |
| case _SignInButtonAction.linkGoogle: |
| await authService.linkWithGoogle(); |
| break; |
| case _SignInButtonAction.unlinkGoogle: |
| await authService.unlinkGoogle(); |
| break; |
| } |
| }, |
| iconSize: Scaffold.of(context).appBarMaxHeight, |
| icon: Builder( |
| builder: (BuildContext context) { |
| if (!kIsWeb && |
| Platform.environment.containsKey('FLUTTER_TEST')) { |
| return Padding( |
| padding: const EdgeInsets.only(right: 10.0, top: 20.0), |
| child: Text(authService.user?.email ?? '[email protected]'), |
| ); |
| } |
| return CircleAvatar( |
| foregroundImage: CachedNetworkImageProvider( |
| authService.user!.photoURL!, |
| imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, |
| ), |
| child: Text( |
| <String?>[ |
| authService.user!.displayName, |
| authService.user!.email, |
| '-', |
| ] |
| .firstWhere( |
| (String? str) => str?.trimLeft().isNotEmpty ?? false, |
| )! |
| .getUserInitials(), |
| textAlign: TextAlign.center, |
| ), |
| ); |
| }, |
| ), |
| ); |
| } |
| return const SignInButton(); |
| }, |
| ); |
| } |
| |
| List<PopupMenuItem<_SignInButtonAction>> _buildLinkUnlinkMenuItem( |
| List<UserInfo> providerData, |
| ) { |
| final items = <PopupMenuItem<_SignInButtonAction>>[]; |
| if (providerData.isNotEmpty) { |
| // One provider linked to firebase user. Show link option for the other. |
| if (providerData.length == 1) { |
| // Linked provider is Google. Show Link GitHub Account option. |
| if (providerData.first.providerId == GoogleAuthProvider.PROVIDER_ID) { |
| items.add( |
| const PopupMenuItem<_SignInButtonAction>( |
| value: _SignInButtonAction.linkGithub, |
| child: Text('Link GitHub Account'), |
| ), |
| ); |
| } |
| // Linked provider is Github. Show Link Google Account option. |
| else if (providerData.first.providerId == |
| GithubAuthProvider.PROVIDER_ID) { |
| items.add( |
| const PopupMenuItem<_SignInButtonAction>( |
| value: _SignInButtonAction.linkGoogle, |
| child: Text('Link Google Account'), |
| ), |
| ); |
| } |
| } |
| // Two providers linked. Show unlink option. |
| else if (providerData.length >= 2) { |
| // The only way to figure out which account was linked is to check order. |
| // If last linked provider is Google. Allow unlinking Google Account. |
| // Thus Google is always primary oAuth provider keeep this option only |
| // for some unexpected cases. |
| if (providerData.last.providerId == GoogleAuthProvider.PROVIDER_ID) { |
| items.add( |
| const PopupMenuItem<_SignInButtonAction>( |
| value: _SignInButtonAction.unlinkGoogle, |
| child: Text('Unlink Google Account'), |
| ), |
| ); |
| } |
| // Pretend we unlink Google account but actualy: |
| // - Unlink Github account. |
| // - Delete Google account. |
| // - Sign in with Github again. |
| else if (providerData.last.providerId == |
| GithubAuthProvider.PROVIDER_ID) { |
| items.add( |
| const PopupMenuItem<_SignInButtonAction>( |
| value: _SignInButtonAction.unlinkGithub, |
| child: Text('Unlink Google Account'), |
| ), |
| ); |
| } |
| } |
| } |
| // Always show logout option. |
| items.add( |
| const PopupMenuItem<_SignInButtonAction>( |
| value: _SignInButtonAction.logout, |
| child: Text('Log out'), |
| ), |
| ); |
| return items; |
| } |
| } |
| |
| extension on String { |
| static final RegExp _splitter = RegExp(r'[ ._-]+'); |
| |
| String getUserInitials() { |
| final parts = |
| split('@') // Split the email into local and domain parts |
| .first // Take only the local part (before the '@' symbol) |
| .split(_splitter); // Split string into a list of substrings |
| |
| // Extract the first character of each non-empty part and join them. |
| final result = parts |
| .where((part) => part.isNotEmpty) // Filter out empty strings from split |
| .map((part) => part[0]) // Get the first character of each part |
| .join() // Join the characters into a single string |
| .toUpperCase(); // Convert to upper case |
| |
| // Ensure no more than 2 characters are returned. |
| return result.length > 2 ? result.substring(0, 2) : result; |
| } |
| } |