snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
This commit is contained in:
14
navigation_and_routing/lib/main.dart
Normal file
14
navigation_and_routing/lib/main.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
import 'src/app.dart';
|
||||
|
||||
void main() {
|
||||
// Removed setupWindow() call
|
||||
runApp(const Bookstore());
|
||||
}
|
||||
|
||||
const double windowWidth = 480;
|
||||
const double windowHeight = 854;
|
||||
279
navigation_and_routing/lib/src/app.dart
Normal file
279
navigation_and_routing/lib/src/app.dart
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'auth.dart';
|
||||
import 'data.dart';
|
||||
import 'screens/author_details.dart';
|
||||
import 'screens/authors.dart';
|
||||
import 'screens/book_details.dart';
|
||||
import 'screens/books.dart';
|
||||
import 'screens/scaffold.dart';
|
||||
import 'screens/settings.dart';
|
||||
import 'screens/sign_in.dart';
|
||||
import 'widgets/book_list.dart';
|
||||
import 'widgets/fade_transition_page.dart';
|
||||
|
||||
final appShellNavigatorKey = GlobalKey<NavigatorState>(
|
||||
debugLabel: 'app shell',
|
||||
);
|
||||
final booksNavigatorKey = GlobalKey<NavigatorState>(
|
||||
debugLabel: 'books shell',
|
||||
);
|
||||
|
||||
class Bookstore extends StatefulWidget {
|
||||
const Bookstore({super.key});
|
||||
|
||||
@override
|
||||
State<Bookstore> createState() => _BookstoreState();
|
||||
}
|
||||
|
||||
class _BookstoreState extends State<Bookstore> {
|
||||
final BookstoreAuth auth = BookstoreAuth();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
builder: (context, child) {
|
||||
if (child == null) {
|
||||
throw ('No child in .router constructor builder');
|
||||
}
|
||||
return BookstoreAuthScope(notifier: auth, child: child);
|
||||
},
|
||||
routerConfig: GoRouter(
|
||||
refreshListenable: auth,
|
||||
debugLogDiagnostics: true,
|
||||
initialLocation: '/books/popular',
|
||||
redirect: (context, state) {
|
||||
final signedIn = BookstoreAuth.of(context).signedIn;
|
||||
if (state.uri.toString() != '/sign-in' && !signedIn) {
|
||||
return '/sign-in';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
ShellRoute(
|
||||
navigatorKey: appShellNavigatorKey,
|
||||
builder: (context, state, child) {
|
||||
return BookstoreScaffold(
|
||||
selectedIndex: switch (state.uri.path) {
|
||||
var p when p.startsWith('/books') => 0,
|
||||
var p when p.startsWith('/authors') => 1,
|
||||
var p when p.startsWith('/settings') => 2,
|
||||
_ => 0,
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
ShellRoute(
|
||||
pageBuilder: (context, state, child) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return BooksScreen(
|
||||
onTap: (idx) {
|
||||
GoRouter.of(context).go(switch (idx) {
|
||||
0 => '/books/popular',
|
||||
1 => '/books/new',
|
||||
2 => '/books/all',
|
||||
_ => '/books/popular',
|
||||
});
|
||||
},
|
||||
selectedIndex: switch (state.uri.path) {
|
||||
var p when p.startsWith('/books/popular') => 0,
|
||||
var p when p.startsWith('/books/new') => 1,
|
||||
var p when p.startsWith('/books/all') => 2,
|
||||
_ => 0,
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/books/popular',
|
||||
pageBuilder: (context, state) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return BookList(
|
||||
books: libraryInstance.popularBooks,
|
||||
onTap: (book) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/books/popular/book/${book.id}');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'book/:bookId',
|
||||
parentNavigatorKey: appShellNavigatorKey,
|
||||
builder: (context, state) {
|
||||
return BookDetailsScreen(
|
||||
book: libraryInstance.getBook(
|
||||
state.pathParameters['bookId'] ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/books/new',
|
||||
pageBuilder: (context, state) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return BookList(
|
||||
books: libraryInstance.newBooks,
|
||||
onTap: (book) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/books/new/book/${book.id}');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'book/:bookId',
|
||||
parentNavigatorKey: appShellNavigatorKey,
|
||||
builder: (context, state) {
|
||||
return BookDetailsScreen(
|
||||
book: libraryInstance.getBook(
|
||||
state.pathParameters['bookId'] ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/books/all',
|
||||
pageBuilder: (context, state) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return BookList(
|
||||
books: libraryInstance.allBooks,
|
||||
onTap: (book) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/books/all/book/${book.id}');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'book/:bookId',
|
||||
parentNavigatorKey: appShellNavigatorKey,
|
||||
builder: (context, state) {
|
||||
return BookDetailsScreen(
|
||||
book: libraryInstance.getBook(
|
||||
state.pathParameters['bookId'] ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/authors',
|
||||
pageBuilder: (context, state) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return AuthorsScreen(
|
||||
onTap: (author) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/authors/author/${author.id}');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'author/:authorId',
|
||||
builder: (context, state) {
|
||||
final author = libraryInstance.allAuthors.firstWhere(
|
||||
(author) =>
|
||||
author.id ==
|
||||
int.parse(state.pathParameters['authorId']!),
|
||||
);
|
||||
// Use a builder to get the correct BuildContext
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return AuthorDetailsScreen(
|
||||
author: author,
|
||||
onBookTapped: (book) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/books/all/book/${book.id}');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
pageBuilder: (context, state) {
|
||||
return FadeTransitionPage<dynamic>(
|
||||
key: state.pageKey,
|
||||
child: const SettingsScreen(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/sign-in',
|
||||
builder: (context, state) {
|
||||
// Use a builder to get the correct BuildContext
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return SignInScreen(
|
||||
onSignIn: (value) async {
|
||||
final router = GoRouter.of(context);
|
||||
await BookstoreAuth.of(
|
||||
context,
|
||||
).signIn(value.username, value.password);
|
||||
router.go('/books/popular');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
47
navigation_and_routing/lib/src/auth.dart
Normal file
47
navigation_and_routing/lib/src/auth.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/widgets.dart';
|
||||
|
||||
/// A mock authentication service
|
||||
class BookstoreAuth extends ChangeNotifier {
|
||||
bool _signedIn = false;
|
||||
|
||||
bool get signedIn => _signedIn;
|
||||
|
||||
Future<void> signOut() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
// Sign out.
|
||||
_signedIn = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is BookstoreAuth && other._signedIn == _signedIn;
|
||||
|
||||
@override
|
||||
int get hashCode => _signedIn.hashCode;
|
||||
|
||||
static BookstoreAuth of(BuildContext context) => context
|
||||
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()!
|
||||
.notifier!;
|
||||
}
|
||||
|
||||
class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
|
||||
const BookstoreAuthScope({
|
||||
required super.notifier,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
}
|
||||
7
navigation_and_routing/lib/src/data.dart
Normal file
7
navigation_and_routing/lib/src/data.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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.
|
||||
|
||||
export 'data/author.dart';
|
||||
export 'data/book.dart';
|
||||
export 'data/library.dart';
|
||||
13
navigation_and_routing/lib/src/data/author.dart
Normal file
13
navigation_and_routing/lib/src/data/author.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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 'book.dart';
|
||||
|
||||
class Author {
|
||||
final int id;
|
||||
final String name;
|
||||
final books = <Book>[];
|
||||
|
||||
Author(this.id, this.name);
|
||||
}
|
||||
15
navigation_and_routing/lib/src/data/book.dart
Normal file
15
navigation_and_routing/lib/src/data/book.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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 'author.dart';
|
||||
|
||||
class Book {
|
||||
final int id;
|
||||
final String title;
|
||||
final Author author;
|
||||
final bool isPopular;
|
||||
final bool isNew;
|
||||
|
||||
Book(this.id, this.title, this.isPopular, this.isNew, this.author);
|
||||
}
|
||||
67
navigation_and_routing/lib/src/data/library.dart
Normal file
67
navigation_and_routing/lib/src/data/library.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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 'author.dart';
|
||||
import 'book.dart';
|
||||
|
||||
final 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,
|
||||
);
|
||||
|
||||
class Library {
|
||||
final List<Book> allBooks = [];
|
||||
final List<Author> allAuthors = [];
|
||||
|
||||
void addBook({
|
||||
required String title,
|
||||
required String authorName,
|
||||
required bool isPopular,
|
||||
required bool isNew,
|
||||
}) {
|
||||
var author = allAuthors.firstWhere(
|
||||
(author) => author.name == authorName,
|
||||
orElse: () {
|
||||
final value = Author(allAuthors.length, authorName);
|
||||
allAuthors.add(value);
|
||||
return value;
|
||||
},
|
||||
);
|
||||
var book = Book(allBooks.length, title, isPopular, isNew, author);
|
||||
|
||||
author.books.add(book);
|
||||
allBooks.add(book);
|
||||
}
|
||||
|
||||
Book getBook(String id) {
|
||||
return allBooks[int.parse(id)];
|
||||
}
|
||||
|
||||
List<Book> get popularBooks => [
|
||||
...allBooks.where((book) => book.isPopular),
|
||||
];
|
||||
|
||||
List<Book> get newBooks => [...allBooks.where((book) => book.isNew)];
|
||||
}
|
||||
38
navigation_and_routing/lib/src/screens/author_details.dart
Normal file
38
navigation_and_routing/lib/src/screens/author_details.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import '../widgets/book_list.dart';
|
||||
|
||||
class AuthorDetailsScreen extends StatelessWidget {
|
||||
final Author author;
|
||||
final ValueChanged<Book> onBookTapped;
|
||||
|
||||
const AuthorDetailsScreen({
|
||||
super.key,
|
||||
required this.author,
|
||||
required this.onBookTapped,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(title: Text(author.name)),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BookList(
|
||||
books: author.books,
|
||||
onTap: (book) {
|
||||
onBookTapped(book);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
26
navigation_and_routing/lib/src/screens/authors.dart
Normal file
26
navigation_and_routing/lib/src/screens/authors.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
import '../data/author.dart';
|
||||
import '../data/library.dart';
|
||||
import '../widgets/author_list.dart';
|
||||
|
||||
class AuthorsScreen extends StatelessWidget {
|
||||
final String title;
|
||||
final ValueChanged<Author> onTap;
|
||||
|
||||
const AuthorsScreen({
|
||||
required this.onTap,
|
||||
this.title = 'Authors',
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(title: Text(title)),
|
||||
body: AuthorList(authors: libraryInstance.allAuthors, onTap: onTap),
|
||||
);
|
||||
}
|
||||
64
navigation_and_routing/lib/src/screens/book_details.dart
Normal file
64
navigation_and_routing/lib/src/screens/book_details.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
|
||||
import '../data.dart';
|
||||
import 'author_details.dart';
|
||||
|
||||
class BookDetailsScreen extends StatelessWidget {
|
||||
final Book? book;
|
||||
|
||||
const BookDetailsScreen({super.key, this.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: [
|
||||
Text(
|
||||
book!.title,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
Text(
|
||||
book!.author.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('View author (Push)'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => AuthorDetailsScreen(
|
||||
author: book!.author,
|
||||
onBookTapped: (book) {
|
||||
GoRouter.of(
|
||||
context,
|
||||
).go('/books/all/book/${book.id}');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Link(
|
||||
uri: Uri.parse('/authors/author/${book!.author.id}'),
|
||||
builder: (context, followLink) => TextButton(
|
||||
onPressed: followLink,
|
||||
child: const Text('View author (Link)'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
navigation_and_routing/lib/src/screens/books.dart
Normal file
69
navigation_and_routing/lib/src/screens/books.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
class BooksScreen extends StatefulWidget {
|
||||
final Widget child;
|
||||
final ValueChanged<int> onTap;
|
||||
final int selectedIndex;
|
||||
|
||||
const BooksScreen({
|
||||
required this.child,
|
||||
required this.onTap,
|
||||
required this.selectedIndex,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@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)
|
||||
..addListener(_handleTabIndexChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant BooksScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.selectedIndex != widget.selectedIndex) {
|
||||
_tabController.index = widget.selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.removeListener(_handleTabIndexChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Books'),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: const [
|
||||
Tab(text: 'Popular', icon: Icon(Icons.people)),
|
||||
Tab(text: 'New', icon: Icon(Icons.new_releases)),
|
||||
Tab(text: 'All', icon: Icon(Icons.list)),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleTabIndexChanged() {
|
||||
widget.onTap(_tabController.index);
|
||||
}
|
||||
}
|
||||
46
navigation_and_routing/lib/src/screens/scaffold.dart
Normal file
46
navigation_and_routing/lib/src/screens/scaffold.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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:adaptive_navigation/adaptive_navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class BookstoreScaffold extends StatelessWidget {
|
||||
final Widget child;
|
||||
final int selectedIndex;
|
||||
|
||||
const BookstoreScaffold({
|
||||
required this.child,
|
||||
required this.selectedIndex,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final goRouter = GoRouter.of(context);
|
||||
|
||||
return Scaffold(
|
||||
body: AdaptiveNavigationScaffold(
|
||||
selectedIndex: selectedIndex,
|
||||
body: child,
|
||||
onDestinationSelected: (idx) {
|
||||
if (idx == 0) goRouter.go('/books/popular');
|
||||
if (idx == 1) goRouter.go('/authors');
|
||||
if (idx == 2) goRouter.go('/settings');
|
||||
},
|
||||
destinations: const [
|
||||
AdaptiveScaffoldDestination(title: 'Books', icon: Icons.book),
|
||||
AdaptiveScaffoldDestination(
|
||||
title: 'Authors',
|
||||
icon: Icons.person,
|
||||
),
|
||||
AdaptiveScaffoldDestination(
|
||||
title: 'Settings',
|
||||
icon: Icons.settings,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
99
navigation_and_routing/lib/src/screens/settings.dart
Normal file
99
navigation_and_routing/lib/src/screens/settings.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
|
||||
import '../auth.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class SettingsContent extends StatelessWidget {
|
||||
const SettingsContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
children: [
|
||||
...[
|
||||
Text(
|
||||
'Settings',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
BookstoreAuth.of(context).signOut();
|
||||
},
|
||||
child: const Text('Sign out'),
|
||||
),
|
||||
const Text('Example using the Link widget:'),
|
||||
Link(
|
||||
uri: Uri.parse('/books/all/book/0'),
|
||||
builder: (context, followLink) => TextButton(
|
||||
onPressed: followLink,
|
||||
child: const Text('/books/all/book/0'),
|
||||
),
|
||||
),
|
||||
const Text('Example using GoRouter.of(context).go():'),
|
||||
TextButton(
|
||||
child: const Text('/books/all/book/0'),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).go('/books/all/book/0');
|
||||
},
|
||||
),
|
||||
].map((w) => Padding(padding: const EdgeInsets.all(8), child: w)),
|
||||
const Text('Displays a dialog on the root Navigator:'),
|
||||
TextButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Alert!'),
|
||||
content: const Text('The alert description goes here.'),
|
||||
actions: [
|
||||
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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
71
navigation_and_routing/lib/src/screens/sign_in.dart
Normal file
71
navigation_and_routing/lib/src/screens/sign_in.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
class Credentials {
|
||||
final String username;
|
||||
final String password;
|
||||
|
||||
Credentials(this.username, this.password);
|
||||
}
|
||||
|
||||
class SignInScreen extends StatefulWidget {
|
||||
final ValueChanged<Credentials> onSignIn;
|
||||
|
||||
const SignInScreen({required this.onSignIn, super.key});
|
||||
|
||||
@override
|
||||
State<SignInScreen> createState() => _SignInScreenState();
|
||||
}
|
||||
|
||||
class _SignInScreenState extends State<SignInScreen> {
|
||||
final _usernameController = TextEditingController();
|
||||
final _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: [
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
24
navigation_and_routing/lib/src/widgets/author_list.dart
Normal file
24
navigation_and_routing/lib/src/widgets/author_list.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
import '../data.dart';
|
||||
|
||||
class AuthorList extends StatelessWidget {
|
||||
final List<Author> authors;
|
||||
final ValueChanged<Author>? onTap;
|
||||
|
||||
const AuthorList({required this.authors, this.onTap, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView.builder(
|
||||
itemCount: authors.length,
|
||||
itemBuilder: (context, index) => ListTile(
|
||||
title: Text(authors[index].name),
|
||||
subtitle: Text('${authors[index].books.length} books'),
|
||||
onTap: onTap != null ? () => onTap!(authors[index]) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
24
navigation_and_routing/lib/src/widgets/book_list.dart
Normal file
24
navigation_and_routing/lib/src/widgets/book_list.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
import '../data.dart';
|
||||
|
||||
class BookList extends StatelessWidget {
|
||||
final List<Book> books;
|
||||
final ValueChanged<Book>? onTap;
|
||||
|
||||
const BookList({required this.books, this.onTap, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListView.builder(
|
||||
itemCount: books.length,
|
||||
itemBuilder: (context, index) => ListTile(
|
||||
title: Text(books[index].title),
|
||||
subtitle: Text(books[index].author.name),
|
||||
onTap: onTap != null ? () => onTap!(books[index]) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// 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/material.dart';
|
||||
|
||||
class FadeTransitionPage<T> extends Page<T> {
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
|
||||
const FadeTransitionPage({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.duration = const Duration(milliseconds: 300),
|
||||
});
|
||||
|
||||
@override
|
||||
Route<T> createRoute(BuildContext context) =>
|
||||
PageBasedFadeTransitionRoute<T>(this);
|
||||
}
|
||||
|
||||
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
|
||||
final FadeTransitionPage<T> _page;
|
||||
|
||||
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
|
||||
|
||||
@override
|
||||
Color? get barrierColor => null;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => null;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => _page.duration;
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Widget buildPage(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(curveTween),
|
||||
child: (settings as FadeTransitionPage).child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) => child;
|
||||
}
|
||||
Reference in New Issue
Block a user