mirror of
https://github.com/flutter/samples.git
synced 2025-11-10 14:58:34 +00:00
[web_dashboard] add logout (#447)
* logout wip * Use AnimatedSwitcher, change lingo from "login" to signIn" * add automatic sign-in * fix flashing sign in button * sign out of FirebaseAuth and GoogleSignIn * formatting * change isSignedIn() to getter * Add error handling for sign in * improve error handling at login screen
This commit is contained in:
@@ -23,7 +23,9 @@ class AppState {
|
||||
AppState(this.auth);
|
||||
}
|
||||
|
||||
/// Creates a [DashboardApi] when the user is logged in.
|
||||
/// Creates a [DashboardApi] for the given user. This allows users of this
|
||||
/// widget to specify whether [MockDashboardApi] or [ApiBuilder] should be
|
||||
/// created when the user logs in.
|
||||
typedef DashboardApi ApiBuilder(User user);
|
||||
|
||||
/// An app that displays a personalized dashboard.
|
||||
@@ -63,41 +65,62 @@ class _DashboardAppState extends State<DashboardApp> {
|
||||
return Provider.value(
|
||||
value: _appState,
|
||||
child: MaterialApp(
|
||||
home: Builder(
|
||||
builder: (context) => SignInPage(
|
||||
auth: _appState.auth,
|
||||
onSuccess: (user) => _handleSignIn(user, context, _appState),
|
||||
),
|
||||
home: SignInSwitcher(
|
||||
appState: _appState,
|
||||
apiBuilder: widget.apiBuilder,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the DashboardApi on AppState and navigates to the home page.
|
||||
void _handleSignIn(User user, BuildContext context, AppState appState) {
|
||||
appState.api = widget.apiBuilder(user);
|
||||
/// Switches between showing the [SignInPage] or [HomePage], depending on
|
||||
/// whether or not the user is signed in.
|
||||
class SignInSwitcher extends StatefulWidget {
|
||||
final AppState appState;
|
||||
final ApiBuilder apiBuilder;
|
||||
|
||||
_showPage(HomePage(), context);
|
||||
}
|
||||
SignInSwitcher({
|
||||
this.appState,
|
||||
this.apiBuilder,
|
||||
});
|
||||
|
||||
/// Navigates to the home page using a fade transition.
|
||||
void _showPage(Widget page, BuildContext context) {
|
||||
var route = _fadeRoute(page);
|
||||
Navigator.of(context).pushReplacement(route);
|
||||
}
|
||||
@override
|
||||
_SignInSwitcherState createState() => _SignInSwitcherState();
|
||||
}
|
||||
|
||||
/// Creates a [Route] that shows [newPage] using a fade transition.
|
||||
Route<FadeTransition> _fadeRoute(Widget newPage) {
|
||||
return PageRouteBuilder<FadeTransition>(
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return newPage;
|
||||
},
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(CurveTween(curve: Curves.ease)),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
class _SignInSwitcherState extends State<SignInSwitcher> {
|
||||
bool _isSignedIn = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSwitcher(
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
duration: Duration(milliseconds: 200),
|
||||
child: _isSignedIn
|
||||
? HomePage(
|
||||
onSignOut: _handleSignOut,
|
||||
)
|
||||
: SignInPage(
|
||||
auth: widget.appState.auth,
|
||||
onSuccess: _handleSignIn,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSignIn(User user) {
|
||||
widget.appState.api = widget.apiBuilder(user);
|
||||
|
||||
setState(() {
|
||||
_isSignedIn = true;
|
||||
});
|
||||
}
|
||||
|
||||
Future _handleSignOut() async {
|
||||
await widget.appState.auth.signOut();
|
||||
setState(() {
|
||||
_isSignedIn = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
abstract class Auth {
|
||||
Future<bool> get isSignedIn;
|
||||
Future<User> signIn();
|
||||
Future signOut();
|
||||
}
|
||||
@@ -10,3 +11,5 @@ abstract class Auth {
|
||||
abstract class User {
|
||||
String get uid;
|
||||
}
|
||||
|
||||
class SignInException implements Exception {}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' hide FirebaseUser;
|
||||
|
||||
@@ -11,9 +12,20 @@ class FirebaseAuthService implements Auth {
|
||||
final GoogleSignIn _googleSignIn = GoogleSignIn();
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
Future<bool> get isSignedIn => _googleSignIn.isSignedIn();
|
||||
|
||||
Future<User> signIn() async {
|
||||
try {
|
||||
return await _signIn();
|
||||
} on PlatformException catch (e) {
|
||||
print('Unable to sign in: $e');
|
||||
throw SignInException();
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> _signIn() async {
|
||||
GoogleSignInAccount googleUser;
|
||||
if (await _googleSignIn.isSignedIn()) {
|
||||
if (await isSignedIn) {
|
||||
googleUser = await _googleSignIn.signInSilently();
|
||||
} else {
|
||||
googleUser = await _googleSignIn.signIn();
|
||||
@@ -30,7 +42,10 @@ class FirebaseAuthService implements Auth {
|
||||
}
|
||||
|
||||
Future<void> signOut() async {
|
||||
await _auth.signOut();
|
||||
await Future.wait([
|
||||
_auth.signOut(),
|
||||
_googleSignIn.signOut(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,3 +54,4 @@ class _FirebaseUser implements User {
|
||||
|
||||
_FirebaseUser(this.uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
// for details. 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:math';
|
||||
|
||||
import 'auth.dart';
|
||||
|
||||
class MockAuthService implements Auth {
|
||||
Future<bool> get isSignedIn async => false;
|
||||
|
||||
@override
|
||||
Future<User> signIn() async {
|
||||
// Sign in will randomly fail 25% of the time.
|
||||
var random = Random();
|
||||
if (random.nextInt(4) == 0) {
|
||||
throw SignInException();
|
||||
}
|
||||
return MockUser();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ import 'dashboard.dart';
|
||||
import 'entries.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
final VoidCallback onSignOut;
|
||||
|
||||
HomePage({
|
||||
@required this.onSignOut,
|
||||
});
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
@@ -20,6 +26,17 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AdaptiveScaffold(
|
||||
title: Text('Dashboard App'),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlatButton(
|
||||
textColor: Colors.white,
|
||||
onPressed: () => _handleSignOut(),
|
||||
child: Text('Sign Out'),
|
||||
),
|
||||
)
|
||||
],
|
||||
currentIndex: _pageIndex,
|
||||
destinations: [
|
||||
AdaptiveScaffoldDestination(title: 'Home', icon: Icons.home),
|
||||
@@ -67,7 +84,36 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _pageAtIndex(int index) {
|
||||
Future<void> _handleSignOut() async {
|
||||
var shouldSignOut = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Are you sure you want to sign out?'),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('No'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Yes'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!shouldSignOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.onSignOut();
|
||||
}
|
||||
|
||||
static Widget _pageAtIndex(int index) {
|
||||
if (index == 0) {
|
||||
return DashboardPage();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../auth/auth.dart';
|
||||
|
||||
class SignInPage extends StatefulWidget {
|
||||
class SignInPage extends StatelessWidget {
|
||||
final Auth auth;
|
||||
final ValueChanged<User> onSuccess;
|
||||
|
||||
@@ -15,31 +15,89 @@ class SignInPage extends StatefulWidget {
|
||||
@required this.onSuccess,
|
||||
});
|
||||
|
||||
@override
|
||||
_SignInPageState createState() => _SignInPageState();
|
||||
}
|
||||
|
||||
class _SignInPageState extends State<SignInPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: RaisedButton(
|
||||
child: Text('Sign In'),
|
||||
onPressed: () async {
|
||||
var user = await widget.auth.signIn();
|
||||
if (user != null) {
|
||||
widget.onSuccess(user);
|
||||
} else {
|
||||
throw ('Unable to sign in');
|
||||
}
|
||||
},
|
||||
),
|
||||
child: SignInButton(auth: auth, onSuccess: onSuccess),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SignInButton extends StatefulWidget {
|
||||
final Auth auth;
|
||||
final ValueChanged<User> onSuccess;
|
||||
|
||||
SignInButton({
|
||||
@required this.auth,
|
||||
@required this.onSuccess,
|
||||
});
|
||||
|
||||
@override
|
||||
_SignInButtonState createState() => _SignInButtonState();
|
||||
}
|
||||
|
||||
class _SignInButtonState extends State<SignInButton> {
|
||||
Future<bool> _checkSignInFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkSignInFuture = _checkIfSignedIn();
|
||||
}
|
||||
|
||||
// Check if the user is signed in. If the user is already signed in (for
|
||||
// example, if they signed in and refreshed the page), invoke the `onSuccess`
|
||||
// callback right away.
|
||||
Future<bool> _checkIfSignedIn() async {
|
||||
var alreadySignedIn = await widget.auth.isSignedIn;
|
||||
if (alreadySignedIn) {
|
||||
var user = await widget.auth.signIn();
|
||||
widget.onSuccess(user);
|
||||
}
|
||||
return alreadySignedIn;
|
||||
}
|
||||
|
||||
Future<void> _signIn() async {
|
||||
try {
|
||||
var user = await widget.auth.signIn();
|
||||
widget.onSuccess(user);
|
||||
} on SignInException {
|
||||
_showError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<bool>(
|
||||
future: _checkSignInFuture,
|
||||
builder: (context, snapshot) {
|
||||
// If signed in, or the future is incomplete, show a circular
|
||||
// progress indicator.
|
||||
var alreadySignedIn = snapshot.data;
|
||||
if (snapshot.connectionState != ConnectionState.done ||
|
||||
alreadySignedIn == true) {
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
|
||||
// If sign in failed, show toast and the login button
|
||||
if (snapshot.hasError) {
|
||||
_showError();
|
||||
}
|
||||
|
||||
return RaisedButton(
|
||||
child: Text('Sign In with Google'),
|
||||
onPressed: () => _signIn(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showError() {
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Unable to sign in.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class AdaptiveScaffoldDestination {
|
||||
/// defined in the [destinations] parameter.
|
||||
class AdaptiveScaffold extends StatefulWidget {
|
||||
final Widget title;
|
||||
final List<Widget> actions;
|
||||
final Widget body;
|
||||
final int currentIndex;
|
||||
final List<AdaptiveScaffoldDestination> destinations;
|
||||
@@ -37,6 +38,7 @@ class AdaptiveScaffold extends StatefulWidget {
|
||||
AdaptiveScaffold({
|
||||
this.title,
|
||||
this.body,
|
||||
this.actions = const [],
|
||||
@required this.currentIndex,
|
||||
@required this.destinations,
|
||||
this.onNavigationIndexChange,
|
||||
@@ -80,7 +82,9 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
|
||||
),
|
||||
Expanded(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(),
|
||||
appBar: AppBar(
|
||||
actions: widget.actions,
|
||||
),
|
||||
body: widget.body,
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
),
|
||||
@@ -94,6 +98,7 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
@@ -126,7 +131,10 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
|
||||
// Show a bottom app bar
|
||||
return Scaffold(
|
||||
body: widget.body,
|
||||
appBar: AppBar(title: widget.title),
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: [
|
||||
...widget.destinations.map(
|
||||
|
||||
Reference in New Issue
Block a user