9
This commit is contained in:
171
lib/13/go/books/main.dart
Normal file
171
lib/13/go/books/main.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2013 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 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'src/auth.dart';
|
||||
import 'src/data/author.dart';
|
||||
import 'src/data/book.dart';
|
||||
import 'src/data/library.dart';
|
||||
import 'src/screens/author_details.dart';
|
||||
import 'src/screens/authors.dart';
|
||||
import 'src/screens/book_details.dart';
|
||||
import 'src/screens/books.dart';
|
||||
import 'src/screens/scaffold.dart';
|
||||
import 'src/screens/settings.dart';
|
||||
import 'src/screens/sign_in.dart';
|
||||
|
||||
void main() => runApp(Bookstore());
|
||||
|
||||
/// The book store view.
|
||||
class Bookstore extends StatelessWidget {
|
||||
/// Creates a [Bookstore].
|
||||
Bookstore({super.key});
|
||||
|
||||
final ValueKey<String> _scaffoldKey = const ValueKey<String>('App scaffold');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => BookstoreAuthScope(
|
||||
notifier: _auth,
|
||||
child: MaterialApp.router(
|
||||
routerConfig: _router,
|
||||
),
|
||||
);
|
||||
|
||||
final BookstoreAuth _auth = BookstoreAuth();
|
||||
|
||||
late final GoRouter _router = GoRouter(
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
redirect: (_, __) => '/books',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/signin',
|
||||
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||
FadeTransitionPage(
|
||||
key: state.pageKey,
|
||||
child: SignInScreen(
|
||||
onSignIn: (Credentials credentials) {
|
||||
BookstoreAuthScope.of(context)
|
||||
.signIn(credentials.username, credentials.password);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/books',
|
||||
redirect: (_, __) => '/books/popular',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/book/:bookId',
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
'/books/all/${state.pathParameters['bookId']}',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/books/:kind(new|all|popular)',
|
||||
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||
FadeTransitionPage(
|
||||
key: _scaffoldKey,
|
||||
child: BookstoreScaffold(
|
||||
selectedTab: ScaffoldTab.books,
|
||||
child: BooksScreen(state.pathParameters['kind']!),
|
||||
),
|
||||
),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: ':bookId',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final String bookId = state.pathParameters['bookId']!;
|
||||
final Book? selectedBook = libraryInstance.allBooks
|
||||
.firstWhereOrNull((Book b) => b.id.toString() == bookId);
|
||||
|
||||
return BookDetailsScreen(book: selectedBook);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/author/:authorId',
|
||||
redirect: (BuildContext context, GoRouterState state) =>
|
||||
'/authors/${state.pathParameters['authorId']}',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/authors',
|
||||
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||
FadeTransitionPage(
|
||||
key: _scaffoldKey,
|
||||
child: const BookstoreScaffold(
|
||||
selectedTab: ScaffoldTab.authors,
|
||||
child: AuthorsScreen(),
|
||||
),
|
||||
),
|
||||
routes: <GoRoute>[
|
||||
GoRoute(
|
||||
path: ':authorId',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final int authorId = int.parse(state.pathParameters['authorId']!);
|
||||
final Author? selectedAuthor = libraryInstance.allAuthors
|
||||
.firstWhereOrNull((Author a) => a.id == authorId);
|
||||
|
||||
return AuthorDetailsScreen(author: selectedAuthor);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||
FadeTransitionPage(
|
||||
key: _scaffoldKey,
|
||||
child: const BookstoreScaffold(
|
||||
selectedTab: ScaffoldTab.settings,
|
||||
child: SettingsScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
redirect: _guard,
|
||||
refreshListenable: _auth,
|
||||
debugLogDiagnostics: true,
|
||||
);
|
||||
|
||||
String? _guard(BuildContext context, GoRouterState state) {
|
||||
final bool signedIn = _auth.signedIn;
|
||||
final bool signingIn = state.matchedLocation == '/signin';
|
||||
|
||||
// Go to /signin if the user is not signed in
|
||||
if (!signedIn && !signingIn) {
|
||||
return '/signin';
|
||||
}
|
||||
// Go to /books if the user is signed in and tries to go to /signin.
|
||||
else if (signedIn && signingIn) {
|
||||
return '/books';
|
||||
}
|
||||
|
||||
// no redirect
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// A page that fades in an out.
|
||||
class FadeTransitionPage extends CustomTransitionPage<void> {
|
||||
/// Creates a [FadeTransitionPage].
|
||||
FadeTransitionPage({
|
||||
required LocalKey super.key,
|
||||
required super.child,
|
||||
}) : super(
|
||||
transitionsBuilder: (BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child) =>
|
||||
FadeTransition(
|
||||
opacity: animation.drive(_curveTween),
|
||||
child: child,
|
||||
));
|
||||
|
||||
static final CurveTween _curveTween = CurveTween(curve: Curves.easeIn);
|
||||
}
|
||||
46
lib/13/go/books/src/auth.dart
Normal file
46
lib/13/go/books/src/auth.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 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 'package:flutter/widgets.dart';
|
||||
|
||||
/// A mock authentication service.
|
||||
class BookstoreAuth extends ChangeNotifier {
|
||||
bool _signedIn = false;
|
||||
|
||||
/// Whether user has signed in.
|
||||
bool get signedIn => _signedIn;
|
||||
|
||||
/// Signs out the current user.
|
||||
Future<void> signOut() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
// Sign out.
|
||||
_signedIn = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Signs in a user.
|
||||
Future<bool> signIn(String username, String password) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
// Sign in. Allow any password.
|
||||
_signedIn = true;
|
||||
notifyListeners();
|
||||
return _signedIn;
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited notifier to host [BookstoreAuth] for the subtree.
|
||||
class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
|
||||
/// Creates a [BookstoreAuthScope].
|
||||
const BookstoreAuthScope({
|
||||
required BookstoreAuth super.notifier,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Gets the [BookstoreAuth] above the context.
|
||||
static BookstoreAuth of(BuildContext context) => context
|
||||
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()!
|
||||
.notifier!;
|
||||
}
|
||||
7
lib/13/go/books/src/data.dart
Normal file
7
lib/13/go/books/src/data.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
export 'data/author.dart';
|
||||
export 'data/book.dart';
|
||||
export 'data/library.dart';
|
||||
23
lib/13/go/books/src/data/author.dart
Normal file
23
lib/13/go/books/src/data/author.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2013 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 'book.dart';
|
||||
|
||||
/// Author data class.
|
||||
class Author {
|
||||
/// Creates an author data object.
|
||||
Author({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
/// The id of the author.
|
||||
final int id;
|
||||
|
||||
/// The name of the author.
|
||||
final String name;
|
||||
|
||||
/// The books of the author.
|
||||
final List<Book> books = <Book>[];
|
||||
}
|
||||
32
lib/13/go/books/src/data/book.dart
Normal file
32
lib/13/go/books/src/data/book.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2013 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 'author.dart';
|
||||
|
||||
/// Book data class.
|
||||
class Book {
|
||||
/// Creates a book data object.
|
||||
Book({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.isPopular,
|
||||
required this.isNew,
|
||||
required this.author,
|
||||
});
|
||||
|
||||
/// The id of the book.
|
||||
final int id;
|
||||
|
||||
/// The title of the book.
|
||||
final String title;
|
||||
|
||||
/// The author of the book.
|
||||
final Author author;
|
||||
|
||||
/// Whether the book is popular.
|
||||
final bool isPopular;
|
||||
|
||||
/// Whether the book is new.
|
||||
final bool isNew;
|
||||
}
|
||||
76
lib/13/go/books/src/data/library.dart
Normal file
76
lib/13/go/books/src/data/library.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2013 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 'author.dart';
|
||||
import 'book.dart';
|
||||
|
||||
/// Library data mock.
|
||||
final Library libraryInstance = Library()
|
||||
..addBook(
|
||||
title: 'Left Hand of Darkness',
|
||||
authorName: 'Ursula K. Le Guin',
|
||||
isPopular: true,
|
||||
isNew: true)
|
||||
..addBook(
|
||||
title: 'Too Like the Lightning',
|
||||
authorName: 'Ada Palmer',
|
||||
isPopular: false,
|
||||
isNew: true)
|
||||
..addBook(
|
||||
title: 'Kindred',
|
||||
authorName: 'Octavia E. Butler',
|
||||
isPopular: true,
|
||||
isNew: false)
|
||||
..addBook(
|
||||
title: 'The Lathe of Heaven',
|
||||
authorName: 'Ursula K. Le Guin',
|
||||
isPopular: false,
|
||||
isNew: false);
|
||||
|
||||
/// A library that contains books and authors.
|
||||
class Library {
|
||||
/// The books in the library.
|
||||
final List<Book> allBooks = <Book>[];
|
||||
|
||||
/// The authors in the library.
|
||||
final List<Author> allAuthors = <Author>[];
|
||||
|
||||
/// Adds a book into the library.
|
||||
void addBook({
|
||||
required String title,
|
||||
required String authorName,
|
||||
required bool isPopular,
|
||||
required bool isNew,
|
||||
}) {
|
||||
final Author author = allAuthors.firstWhere(
|
||||
(Author author) => author.name == authorName,
|
||||
orElse: () {
|
||||
final Author value = Author(id: allAuthors.length, name: authorName);
|
||||
allAuthors.add(value);
|
||||
return value;
|
||||
},
|
||||
);
|
||||
|
||||
final Book book = Book(
|
||||
id: allBooks.length,
|
||||
title: title,
|
||||
isPopular: isPopular,
|
||||
isNew: isNew,
|
||||
author: author,
|
||||
);
|
||||
|
||||
author.books.add(book);
|
||||
allBooks.add(book);
|
||||
}
|
||||
|
||||
/// The list of popular books in the library.
|
||||
List<Book> get popularBooks => <Book>[
|
||||
...allBooks.where((Book book) => book.isPopular),
|
||||
];
|
||||
|
||||
/// The list of new books in the library.
|
||||
List<Book> get newBooks => <Book>[
|
||||
...allBooks.where((Book book) => book.isNew),
|
||||
];
|
||||
}
|
||||
49
lib/13/go/books/src/screens/author_details.dart
Normal file
49
lib/13/go/books/src/screens/author_details.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import '../widgets/book_list.dart';
|
||||
|
||||
/// The author detail screen.
|
||||
class AuthorDetailsScreen extends StatelessWidget {
|
||||
/// Creates an author detail screen.
|
||||
const AuthorDetailsScreen({
|
||||
required this.author,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The author to be displayed.
|
||||
final Author? author;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (author == null) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('No author found.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(author!.name),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BookList(
|
||||
books: author!.books,
|
||||
onTap: (Book book) => context.go('/book/${book.id}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/13/go/books/src/screens/authors.dart
Normal file
31
lib/13/go/books/src/screens/authors.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import '../widgets/author_list.dart';
|
||||
|
||||
/// A screen that displays a list of authors.
|
||||
class AuthorsScreen extends StatelessWidget {
|
||||
/// Creates an [AuthorsScreen].
|
||||
const AuthorsScreen({super.key});
|
||||
|
||||
/// The title of the screen.
|
||||
static const String title = 'Authors';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(title),
|
||||
),
|
||||
body: AuthorList(
|
||||
authors: libraryInstance.allAuthors,
|
||||
onTap: (Author author) {
|
||||
context.go('/author/${author.id}');
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
77
lib/13/go/books/src/screens/book_details.dart
Normal file
77
lib/13/go/books/src/screens/book_details.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import 'author_details.dart';
|
||||
|
||||
/// A screen to display book details.
|
||||
class BookDetailsScreen extends StatelessWidget {
|
||||
/// Creates a [BookDetailsScreen].
|
||||
const BookDetailsScreen({
|
||||
super.key,
|
||||
this.book,
|
||||
});
|
||||
|
||||
/// The book to be displayed.
|
||||
final Book? book;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (book == null) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('No book found.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(book!.title),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
book!.title,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
Text(
|
||||
book!.author.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) =>
|
||||
AuthorDetailsScreen(author: book!.author),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('View author (navigator.push)'),
|
||||
),
|
||||
Link(
|
||||
uri: Uri.parse('/author/${book!.author.id}'),
|
||||
builder: (BuildContext context, FollowLink? followLink) =>
|
||||
TextButton(
|
||||
onPressed: followLink,
|
||||
child: const Text('View author (Link)'),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.push('/author/${book!.author.id}');
|
||||
},
|
||||
child: const Text('View author (GoRouter.push)'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
118
lib/13/go/books/src/screens/books.dart
Normal file
118
lib/13/go/books/src/screens/books.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import '../widgets/book_list.dart';
|
||||
|
||||
/// A screen that displays a list of books.
|
||||
class BooksScreen extends StatefulWidget {
|
||||
/// Creates a [BooksScreen].
|
||||
const BooksScreen(this.kind, {super.key});
|
||||
|
||||
/// Which tab to display.
|
||||
final String kind;
|
||||
|
||||
@override
|
||||
State<BooksScreen> createState() => _BooksScreenState();
|
||||
}
|
||||
|
||||
class _BooksScreenState extends State<BooksScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(BooksScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
switch (widget.kind) {
|
||||
case 'popular':
|
||||
_tabController.index = 0;
|
||||
break;
|
||||
|
||||
case 'new':
|
||||
_tabController.index = 1;
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
_tabController.index = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Books'),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
onTap: _handleTabTapped,
|
||||
tabs: const <Tab>[
|
||||
Tab(
|
||||
text: 'Popular',
|
||||
icon: Icon(Icons.people),
|
||||
),
|
||||
Tab(
|
||||
text: 'New',
|
||||
icon: Icon(Icons.new_releases),
|
||||
),
|
||||
Tab(
|
||||
text: 'All',
|
||||
icon: Icon(Icons.list),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: <Widget>[
|
||||
BookList(
|
||||
books: libraryInstance.popularBooks,
|
||||
onTap: _handleBookTapped,
|
||||
),
|
||||
BookList(
|
||||
books: libraryInstance.newBooks,
|
||||
onTap: _handleBookTapped,
|
||||
),
|
||||
BookList(
|
||||
books: libraryInstance.allBooks,
|
||||
onTap: _handleBookTapped,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
void _handleBookTapped(Book book) {
|
||||
context.go('/book/${book.id}');
|
||||
}
|
||||
|
||||
void _handleTabTapped(int index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
context.go('/books/new');
|
||||
break;
|
||||
case 2:
|
||||
context.go('/books/all');
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
context.go('/books/popular');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
lib/13/go/books/src/screens/scaffold.dart
Normal file
71
lib/13/go/books/src/screens/scaffold.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2013 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 'package:adaptive_navigation/adaptive_navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// The enum for scaffold tab.
|
||||
enum ScaffoldTab {
|
||||
/// The books tab.
|
||||
books,
|
||||
|
||||
/// The authors tab.
|
||||
authors,
|
||||
|
||||
/// The settings tab.
|
||||
settings
|
||||
}
|
||||
|
||||
/// The scaffold for the book store.
|
||||
class BookstoreScaffold extends StatelessWidget {
|
||||
/// Creates a [BookstoreScaffold].
|
||||
const BookstoreScaffold({
|
||||
required this.selectedTab,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Which tab of the scaffold to display.
|
||||
final ScaffoldTab selectedTab;
|
||||
|
||||
/// The scaffold body.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: AdaptiveNavigationScaffold(
|
||||
selectedIndex: selectedTab.index,
|
||||
body: child,
|
||||
onDestinationSelected: (int idx) {
|
||||
switch (ScaffoldTab.values[idx]) {
|
||||
case ScaffoldTab.books:
|
||||
context.go('/books');
|
||||
break;
|
||||
case ScaffoldTab.authors:
|
||||
context.go('/authors');
|
||||
break;
|
||||
case ScaffoldTab.settings:
|
||||
context.go('/settings');
|
||||
break;
|
||||
}
|
||||
},
|
||||
destinations: const <AdaptiveScaffoldDestination>[
|
||||
AdaptiveScaffoldDestination(
|
||||
title: 'Books',
|
||||
icon: Icons.book,
|
||||
),
|
||||
AdaptiveScaffoldDestination(
|
||||
title: 'Authors',
|
||||
icon: Icons.person,
|
||||
),
|
||||
AdaptiveScaffoldDestination(
|
||||
title: 'Settings',
|
||||
icon: Icons.settings,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
101
lib/13/go/books/src/screens/settings.dart
Normal file
101
lib/13/go/books/src/screens/settings.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
|
||||
import '../auth.dart';
|
||||
|
||||
/// The settings screen.
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
/// Creates a [SettingsScreen].
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: const Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 12),
|
||||
child: SettingsContent(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The content of a [SettingsScreen].
|
||||
class SettingsContent extends StatelessWidget {
|
||||
/// Creates a [SettingsContent].
|
||||
const SettingsContent({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
children: <Widget>[
|
||||
...<Widget>[
|
||||
Text(
|
||||
'Settings',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
BookstoreAuthScope.of(context).signOut();
|
||||
},
|
||||
child: const Text('Sign out'),
|
||||
),
|
||||
Link(
|
||||
uri: Uri.parse('/book/0'),
|
||||
builder: (BuildContext context, FollowLink? followLink) =>
|
||||
TextButton(
|
||||
onPressed: followLink,
|
||||
child: const Text('Go directly to /book/0 (Link)'),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.go('/book/0');
|
||||
},
|
||||
child: const Text('Go directly to /book/0 (GoRouter)'),
|
||||
),
|
||||
].map<Widget>((Widget w) =>
|
||||
Padding(padding: const EdgeInsets.all(8), child: w)),
|
||||
TextButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: const Text('Alert!'),
|
||||
content: const Text('The alert description goes here.'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'OK'),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: const Text('Show Dialog'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
77
lib/13/go/books/src/screens/sign_in.dart
Normal file
77
lib/13/go/books/src/screens/sign_in.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
|
||||
/// Credential data class.
|
||||
class Credentials {
|
||||
/// Creates a credential data object.
|
||||
Credentials(this.username, this.password);
|
||||
|
||||
/// The username of the credentials.
|
||||
final String username;
|
||||
|
||||
/// The password of the credentials.
|
||||
final String password;
|
||||
}
|
||||
|
||||
/// The sign-in screen.
|
||||
class SignInScreen extends StatefulWidget {
|
||||
/// Creates a sign-in screen.
|
||||
const SignInScreen({
|
||||
required this.onSignIn,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Called when users sign in with [Credentials].
|
||||
final ValueChanged<Credentials> onSignIn;
|
||||
|
||||
@override
|
||||
State<SignInScreen> createState() => _SignInScreenState();
|
||||
}
|
||||
|
||||
class _SignInScreenState extends State<SignInScreen> {
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: Center(
|
||||
child: Card(
|
||||
child: Container(
|
||||
constraints: BoxConstraints.loose(const Size(600, 600)),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('Sign in',
|
||||
style: Theme.of(context).textTheme.headlineMedium),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Username'),
|
||||
controller: _usernameController,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Password'),
|
||||
obscureText: true,
|
||||
controller: _passwordController,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
widget.onSignIn(Credentials(
|
||||
_usernameController.value.text,
|
||||
_passwordController.value.text));
|
||||
},
|
||||
child: const Text('Sign in'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
37
lib/13/go/books/src/widgets/author_list.dart
Normal file
37
lib/13/go/books/src/widgets/author_list.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
|
||||
import '../data.dart';
|
||||
|
||||
/// The author list view.
|
||||
class AuthorList extends StatelessWidget {
|
||||
/// Creates an [AuthorList].
|
||||
const AuthorList({
|
||||
required this.authors,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The list of authors to be shown.
|
||||
final List<Author> authors;
|
||||
|
||||
/// Called when the user taps an author.
|
||||
final ValueChanged<Author>? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView.builder(
|
||||
itemCount: authors.length,
|
||||
itemBuilder: (BuildContext context, int index) => ListTile(
|
||||
title: Text(
|
||||
authors[index].name,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${authors[index].books.length} books',
|
||||
),
|
||||
onTap: onTap != null ? () => onTap!(authors[index]) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
37
lib/13/go/books/src/widgets/book_list.dart
Normal file
37
lib/13/go/books/src/widgets/book_list.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 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 'package:flutter/material.dart';
|
||||
|
||||
import '../data.dart';
|
||||
|
||||
/// The book list view.
|
||||
class BookList extends StatelessWidget {
|
||||
/// Creates an [BookList].
|
||||
const BookList({
|
||||
required this.books,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The list of books to be displayed.
|
||||
final List<Book> books;
|
||||
|
||||
/// Called when the user taps a book.
|
||||
final ValueChanged<Book>? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView.builder(
|
||||
itemCount: books.length,
|
||||
itemBuilder: (BuildContext context, int index) => ListTile(
|
||||
title: Text(
|
||||
books[index].title,
|
||||
),
|
||||
subtitle: Text(
|
||||
books[index].author.name,
|
||||
),
|
||||
onTap: onTap != null ? () => onTap!(books[index]) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user