Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dogfooding/lib/core/model/environment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ enum Environment {
final List<String> aliases;
final List<String> baseUrls;

/// Whether this is a Pronto environment.
bool get isPronto => envName == 'pronto';

String? getJoinUrl({required String callId, String? callType}) {
switch (this) {
case Environment.pronto:
Expand Down
15 changes: 8 additions & 7 deletions dogfooding/lib/core/repos/user_auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ class UserAuthRepository {
Future<UserCredentials> login() async {
final response = await videoClient.connect();

return response.fold(
success: (success) {
return UserCredentials(token: success.data, userInfo: currentUser);
},
failure: (failure) {
throw failure.error;
},
if (response is Failure) {
await videoClient.disconnect();
throw response.error;
}

return UserCredentials(
token: (response as Success<UserToken>).data,
userInfo: currentUser,
);
}

Expand Down
14 changes: 9 additions & 5 deletions dogfooding/lib/di/injector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ class AppInjector {

// Register dependencies
static Future<void> init({Environment? forceEnvironment}) async {
// Google sign in
locator.registerSingletonAsync<GoogleSignIn>(() async {
await GoogleSignIn.instance.initialize(hostedDomain: 'getstream.io');
return GoogleSignIn.instance;
});
// Google sign in. On web we use Firebase Auth's popup flow instead of the
// google_sign_in plugin (see LoginScreen), so we only initialize the
// plugin on native platforms.
if (!kIsWeb) {
locator.registerSingletonAsync<GoogleSignIn>(() async {
await GoogleSignIn.instance.initialize(hostedDomain: 'getstream.io');
return GoogleSignIn.instance;
});
}

// App Preferences
final prefs = await SharedPreferences.getInstance();
Expand Down
177 changes: 132 additions & 45 deletions dogfooding/lib/screens/login_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' as math;

import 'package:firebase_auth/firebase_auth.dart' as fb;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
Expand Down Expand Up @@ -28,48 +29,120 @@ class LoginScreen extends StatefulWidget {

class _LoginScreenState extends State<LoginScreen> {
final _appPreferences = locator<AppPreferences>();
final _emailController = TextEditingController();
final _usernameController = TextEditingController();

Future<void> _loginWithGoogle() async {
final googleService = locator<GoogleSignIn>();
StreamSubscription<GoogleSignInAuthenticationEvent>? _googleAuthSubscription;
bool _isLoggingIn = false;

@override
void initState() {
super.initState();
unawaited(_initGoogleSignIn());
}

/// Subscribes to Google auth events so the result of the mobile/macOS
/// `authenticate()` call is handled. Web uses Firebase Auth's popup flow
/// instead (see [_loginWithGoogleWeb]), so there is no google_sign_in plugin
/// to subscribe to there.
Future<void> _initGoogleSignIn() async {
if (kIsWeb) return;

final googleService = await locator.getAsync<GoogleSignIn>();
_googleAuthSubscription = googleService.authenticationEvents.listen(
_handleGoogleAuthEvent,
onError: (Object e) {
debugPrint('Google auth event error: $e');
_showSnackBar('Google sign-in failed: $e');
},
);
}
Comment thread
Brazol marked this conversation as resolved.

void _handleGoogleAuthEvent(GoogleSignInAuthenticationEvent event) {
if (event is! GoogleSignInAuthenticationEventSignIn) return;

final googleUser = event.user;
unawaited(
_completeGoogleLogin(
email: googleUser.email,
name: googleUser.displayName ?? '',
image: googleUser.photoUrl,
),
);
}

GoogleSignInAccount? googleUser;
/// Triggers an interactive Google sign-in from the button tap.
Future<void> _loginWithGoogle() async {
try {
googleUser = await googleService.attemptLightweightAuthentication();
if (kIsWeb) {
await _loginWithGoogleWeb();
return;
}

final googleService = await locator.getAsync<GoogleSignIn>();
if (googleService.supportsAuthenticate()) {
// Result delivered via authenticationEvents -> _handleGoogleAuthEvent.
await googleService.authenticate();
}
} catch (e) {
debugPrint('Google lightweight auth failed: $e');
debugPrint('Google sign-in failed: $e');
_showSnackBar('Google sign-in failed: $e');
}
}

if (googleUser == null && googleService.supportsAuthenticate()) {
try {
googleUser = await googleService.authenticate();
} catch (e) {
return debugPrint('Google sign-in failed: $e');
/// Web Google sign-in via Firebase Auth's popup flow.
Future<void> _loginWithGoogleWeb() async {
try {
final provider = fb.GoogleAuthProvider()
// Hints Google to restrict the account chooser to the Stream domain.
..setCustomParameters(const {'hd': 'getstream.io'});

final credential =
await fb.FirebaseAuth.instance.signInWithPopup(provider);
final user = credential.user;
if (user == null) {
debugPrint('Google sign-in returned no user');
_showSnackBar('Google sign-in failed: no user returned.');
return;
}

await _completeGoogleLogin(
email: user.email,
name: user.displayName ?? '',
image: user.photoURL,
);
} catch (e) {
debugPrint('Google sign-in failed: $e');
_showSnackBar('Google sign-in failed: $e');
}
}
Comment thread
Brazol marked this conversation as resolved.

if (googleUser == null) {
return debugPrint('Google login cancelled or unavailable');
Future<void> _completeGoogleLogin({
required String? email,
required String name,
String? image,
}) async {
if (email == null || email.isEmpty) {
debugPrint('Google sign-in returned no email');
_showSnackBar('Google sign-in failed: no email address returned.');
return;
}

final userInfo = UserInfo(
role: 'admin',
id: createValidId(googleUser.email),
name: googleUser.displayName ?? '',
image: googleUser.photoUrl,
id: createValidId(email),
name: name,
image: image,
);

return _login(User(info: userInfo), _appPreferences.environment);
await _login(User(info: userInfo), _appPreferences.environment);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Future<void> _loginWithEmail() async {
final email = _emailController.text;
if (email.isEmpty) return debugPrint('Email is empty');
Future<void> _loginWithUsername() async {
final username = _usernameController.text;
if (username.isEmpty) return debugPrint('Username is empty');

final userInfo = UserInfo(
role: 'admin',
id: createValidId(email),
name: email,
id: createValidId(username),
name: username,
);

return _login(User(info: userInfo), _appPreferences.environment);
Expand All @@ -78,7 +151,6 @@ class _LoginScreenState extends State<LoginScreen> {
Future<void> _loginAsGuest() async {
final userId = randomId(size: 6);
final userInfo = UserInfo(
role: 'admin',
id: userId,
name: userId,
image:
Expand All @@ -92,6 +164,9 @@ class _LoginScreenState extends State<LoginScreen> {
}

Future<void> _login(User user, Environment environment) async {
if (_isLoggingIn) return;
_isLoggingIn = true;

if (mounted) unawaited(showLoadingIndicator(context));

// Register StreamVideo client with the user.
Expand All @@ -100,21 +175,27 @@ class _LoginScreenState extends State<LoginScreen> {
try {
await authController.login(user, environment);
} catch (e, _) {
if (mounted) {
hideLoadingIndicator(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 20),
content: Text('Error: $e'),
),
);
}
if (mounted) hideLoadingIndicator(context);
_showSnackBar('Error: $e');
} finally {
_isLoggingIn = false;
}
}

void _showSnackBar(String message) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 20),
content: Text(message),
),
);
}

@override
void dispose() {
_emailController.dispose();
unawaited(_googleAuthSubscription?.cancel());
_usernameController.dispose();
super.dispose();
}

Expand All @@ -131,6 +212,8 @@ class _LoginScreenState extends State<LoginScreen> {
if (!kIsProd)
EnvironmentSwitcher(
currentEnvironment: _appPreferences.environment,
// Rebuild so the Google button visibility tracks the environment.
onEnvironmentChanged: (_) => setState(() {}),
),
],
),
Expand Down Expand Up @@ -162,12 +245,12 @@ class _LoginScreenState extends State<LoginScreen> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextField(
controller: _emailController,
controller: _usernameController,
style: theme.textTheme.bodyMedium?.apply(
color: Colors.white,
),
decoration: const InputDecoration(
labelText: 'Enter Email',
labelText: 'Enter Username',
isDense: true,
border: OutlineInputBorder(),
Comment on lines -170 to 255

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add an errorText like here: https://github.com/GetStream/stream-video-flutter/pull/1272/changes#diff-c37bf48e33cc1417b6d455379a1f104f36a39271578670c5c35a01bda1d67d2eR184-R186

Currently we only do a debugPrint when the name is empty.

),
Expand All @@ -177,12 +260,12 @@ class _LoginScreenState extends State<LoginScreen> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: StreamButton.active(
label: 'Sign up with email',
label: 'Sign up with username',
icon: const Icon(
Icons.email_outlined,
Icons.person_outline,
color: Colors.white,
),
onPressed: _loginWithEmail,
onPressed: _loginWithUsername,
),
),
const SizedBox(height: 16),
Expand All @@ -203,11 +286,15 @@ class _LoginScreenState extends State<LoginScreen> {
],
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GoogleLoginButton(onPressed: _loginWithGoogle),
),
// Google (Stream employee) sign-in is only available on
// Pronto environments.
if (_appPreferences.environment.isPronto) ...[
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GoogleLoginButton(onPressed: _loginWithGoogle),
),
],
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
Expand Down
11 changes: 10 additions & 1 deletion dogfooding/lib/widgets/environment_switcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import '../di/injector.dart';
import '../theme/app_palette.dart';

class EnvironmentSwitcher extends StatefulWidget {
const EnvironmentSwitcher({super.key, required this.currentEnvironment});
const EnvironmentSwitcher({
super.key,
required this.currentEnvironment,
this.onEnvironmentChanged,
});

final Environment currentEnvironment;

/// Called after the user selects a different environment.
final ValueChanged<Environment>? onEnvironmentChanged;

@override
State<EnvironmentSwitcher> createState() => _EnvironmentSwitcherState();
}
Expand Down Expand Up @@ -84,6 +91,8 @@ class _EnvironmentSwitcherState extends State<EnvironmentSwitcher> {
setState(() {
selectedEnvironment = env;
});

widget.onEnvironmentChanged?.call(env);
},
child: Container(
width: 100,
Expand Down
20 changes: 0 additions & 20 deletions dogfooding/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@
<meta name="apple-mobile-web-app-title" content="dogfooding">
<link rel="apple-touch-icon" href="icons/Icon-192.png">

<!-- Google Sign In -->
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id"
content="347024607410-ett7cjt6ah9aj6s6k20p5fissj80d9la.apps.googleusercontent.com">

<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png" />

Expand Down Expand Up @@ -62,21 +57,6 @@
});
</script>

<script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-app.js"></script>

<script>
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: 'AIzaSyB1swZGD2U9qEKV4xYKlr9KBHeysTHJ_1w',
authDomain: 'stream-video-9b586.firebaseapp.com',
projectId: 'stream-video-9b586',
messagingSenderId: '347024607410',
appId: '1:347024607410:web:fd70974cbc1256bb8c21ab',
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
</script>

<script src="main.dart.js" type="application/javascript"></script>
</body>

Expand Down
Loading