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

This commit is contained in:
upstream-mirror
2026-09-16 01:43:26 +05:00
commit 9ed6a80afa
2886 changed files with 140184 additions and 0 deletions

View 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;

View 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');
},
);
},
);
},
),
],
),
);
}
}

View 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,
});
}

View 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';

View 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);
}

View 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);
}

View 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)];
}

View 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);
},
),
),
],
),
),
);
}

View 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),
);
}

View 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)'),
),
),
],
),
),
);
}
}

View 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);
}
}

View 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,
),
],
),
);
}
}

View 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'),
),
],
);
}

View 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'),
),
),
],
),
),
),
),
);
}

View 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,
),
);
}

View 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,
),
);
}

View File

@@ -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;
}