diff --git a/examples/bottom_navigation_complex/.gitignore b/examples/bottom_navigation_complex/.gitignore new file mode 100644 index 0000000..f28b7b4 --- /dev/null +++ b/examples/bottom_navigation_complex/.gitignore @@ -0,0 +1,55 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Remove platform specific folders +android +ios +linux +macos +web +windows diff --git a/examples/bottom_navigation_complex/.metadata b/examples/bottom_navigation_complex/.metadata new file mode 100644 index 0000000..245a8e8 --- /dev/null +++ b/examples/bottom_navigation_complex/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 85684f9300908116a78138ea4c6036c35c9a1236 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 85684f9300908116a78138ea4c6036c35c9a1236 + base_revision: 85684f9300908116a78138ea4c6036c35c9a1236 + - platform: android + create_revision: 85684f9300908116a78138ea4c6036c35c9a1236 + base_revision: 85684f9300908116a78138ea4c6036c35c9a1236 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/examples/bottom_navigation_complex/README.md b/examples/bottom_navigation_complex/README.md new file mode 100644 index 0000000..c997bbf --- /dev/null +++ b/examples/bottom_navigation_complex/README.md @@ -0,0 +1,6 @@ +# Bottom Navigation Complex + +

+ + +Run `flutter create .` to generate all necessary files, if needed. \ No newline at end of file diff --git a/examples/bottom_navigation_complex/analysis_options.yaml b/examples/bottom_navigation_complex/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/examples/bottom_navigation_complex/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/examples/bottom_navigation_complex/bottom_navigation_complex.drawio b/examples/bottom_navigation_complex/bottom_navigation_complex.drawio new file mode 100644 index 0000000..783fc69 --- /dev/null +++ b/examples/bottom_navigation_complex/bottom_navigation_complex.drawio @@ -0,0 +1 @@ +7VvbcuI4EP0aqnYfoGzJ10cuSSa1ma3ZYi/Jo2ILcGIsxogE8vUr2xK2LJMhg43NVHhIrNYF63SfVqslenC83N7EaLX4Snwc9oDmb3tw0gMA6pbG/iWSXSYBrm5lknkc+JlMzwXT4A1zIe843wQ+XksNKSEhDVay0CNRhD0qyVAck1e52YyE8reu0BwrgqmHQlX6X+DTRSZ1gJ3Lv+BgvhDfrFtuVrNEojGfyXqBfPJaEMGrHhzHhNDsabkd4zBBT+CS9bs+ULt/sRhH9JgODzv39vn67/U/Nnjy3+6n3s2T3Yd8Hi8o3PAZ87elOwFBTDaRj5NRtB4cvS4Ciqcr5CW1r0zrTLagy5CVdPY4IxHlWtST5rMgDMckJHE6FvQRdmYek69pTJ5xocbyHPw4YzXqvPhUX3BM8bYg4vO8wWSJabxjTXitLTDnVtc3jKz8mqvQEm0WBfW5Jhcibjbz/dg5suyBg1sN9P2fN9+H326f/nr78seVM4Hju383fUOBFfvM0HiRxHRB5iRC4VUuHcnA523uCFlxuJ8wpTuON9pQIisDbwN6n3QfmLz0UKiZbPnIaWEnChGbbqFTUnwo1uXd0pLol80vmdRB++SiNdnEHn7HKE3OcxTPMX0HU1htBDEOEQ1e5Pc4RaHvvWSBOGNm+slcT+JPDdbv6KZk/abmDICrEABoFQSAVg0EqMTLUvCaYhR7i8uGqyl34X66i6PdhX2ku7DbdBfqOtsVd6FrVhf9haMA9nU3XSTx3GUD1hReOlAAG2G0xHHreLlGKRrTB7auFT5O27amQwW7O7Qjm/bJWRt2jUFnKND1gBVSvhWQ0LO+b4io6K/TVWjIGujaapuiJerZ0zz5PyLkeS1GY2+XDZjVdU4zEFQ6Af282mg1bhDPzUUN5d3lQT0Wg4H3wtFSAHJCdMC7fiNBavX7taJsJiYzE8fNP5Y8ZBbt8FFyaxjGMdoVmq2SBusPfK8BS9mBUge3FAWX2rOH7BVy29yD9PPmKnggOY/rA/uCRFV36BGHsvGhMJhH7NljJsAWOzhKKBx4KBzyimXg+5mdY+Zz0GM6XmI8HEM2uDnqmZMK8wqTrxsh73meckRkKyIS4Urje5eTimvZJ634O/WKeaEql6MNLNOW1dTnI/2smYomZDZbY9or+58adLxPf7WzlemOS/rhPkZkRn/ou5xGfFfjLkbXrTZ8jFjZJR9zaC9xqU5GsKwWJ2MZQHYyoOtORlezS7VFocNE0yHubCCqhhhdiESB2arbP0MGqz7HD8CRjl/sU+vz/KfpWM3RsHH4rk3r9xJPc53Tp31Xe6wzFbn9GpypBsRRYXe9J7A+uVo7V4HRLa5WHfSmXE3HHT6yp9vJJZFULPk1kBTowpXtpIFO3PwbUF6XbXmA5ggNPxff+gkNu0VoWBXyXk+ZhoJofkmrLaxxtXV17UTq7m+MmA2QtfJYUgR1vy5ZK3lTH1urUQWtrrZAIac4jMs2kY+x2D/+FkQBDVCYAsL2aszG0/2oiKR/V6zj7BtM4JYOoUxHvU915iM7oB7ZdeZA3bC6fuAJ1FO7LBTUJpiiIOzAIXttIDaFIeywBbqdt0CoWuDB2OVy4WsMvarMywfznfaBfOeUIoqTF0iSOCsW9uGYLZdHJj8ZwlTWh3zRl2e1i7eCuej4SLTKAGQTOXp38IErxbBkEkluR/rYik3sb78XbcJozCZcxSaGq1X7ZDohemjqdmXlkRDPW3YnGWLvFXD01qhCV4cvsJRO/F1oMYs2gGPaOvtrgYbAV0PjGpfNCqSOR6R8E9i1rTMuk4evuDccp7WEWVOQqRfzpVOIhN8oK/3yFNf1UiBzLo6ruamucFy5vNsBkqupeW6uHeL5Cbg1topX3h7bQ5cQfdgVomsNE93R3IFTCEaBpKu+bQ3swp1D1zyskgP50JKnaDAdqgZnnfEdtt6hbfU7ac+u+5LacPw4jKyY/+Q2s9n8l8vw6n8= \ No newline at end of file diff --git a/examples/bottom_navigation_complex/bottom_navigation_complex.png b/examples/bottom_navigation_complex/bottom_navigation_complex.png new file mode 100644 index 0000000..e7819ed Binary files /dev/null and b/examples/bottom_navigation_complex/bottom_navigation_complex.png differ diff --git a/examples/bottom_navigation_complex/lib/app.dart b/examples/bottom_navigation_complex/lib/app.dart new file mode 100644 index 0000000..37c658b --- /dev/null +++ b/examples/bottom_navigation_complex/lib/app.dart @@ -0,0 +1,20 @@ +import 'package:bottom_navigation_complex/routers/app.router.dart'; +import 'package:bottom_navigation_complex/screens/splash.screen.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +class App extends StatelessWidget { + static final BeamerDelegate _router = routerDelegate; + + const App({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + debugShowCheckedModeBanner: false, + routerDelegate: _router, + routeInformationParser: BeamerParser(), + builder: (context, child) => SplashScreen(screen: child), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/data/data.dart b/examples/bottom_navigation_complex/lib/data/data.dart new file mode 100644 index 0000000..e6d1f3b --- /dev/null +++ b/examples/bottom_navigation_complex/lib/data/data.dart @@ -0,0 +1,13 @@ +import 'package:bottom_navigation_complex/models/article.models.dart'; +import 'package:bottom_navigation_complex/models/book.models.dart'; + +const List books = [ + Book(id: '1', title: 'Stranger in a Strange Land', author: 'Robert A. Heinlein'), + Book(id: '2', title: 'Foundation', author: 'Isaac Asimov'), + Book(id: '3', title: 'Fahrenheit 451', author: 'Ray Bradbury'), +]; + +const List

articles = [ + Article(id: '1', title: 'Learning Flutter’s new navigation and routing system', seller: 'John Ryan'), + Article(id: '2', title: 'Explaining Flutter Nav 2.0 and Beamer', seller: 'Isaac Asimov'), +]; diff --git a/examples/bottom_navigation_complex/lib/main.dart b/examples/bottom_navigation_complex/lib/main.dart new file mode 100644 index 0000000..f2da9dc --- /dev/null +++ b/examples/bottom_navigation_complex/lib/main.dart @@ -0,0 +1,8 @@ +import 'package:bottom_navigation_complex/app.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +void main() { + Beamer.setPathUrlStrategy(); + runApp(const App()); +} diff --git a/examples/bottom_navigation_complex/lib/models/article.models.dart b/examples/bottom_navigation_complex/lib/models/article.models.dart new file mode 100644 index 0000000..a7d9371 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/models/article.models.dart @@ -0,0 +1,7 @@ +class Article { + final String id; + final String title; + final String seller; + + const Article({required this.id, required this.title, required this.seller}); +} diff --git a/examples/bottom_navigation_complex/lib/models/book.models.dart b/examples/bottom_navigation_complex/lib/models/book.models.dart new file mode 100644 index 0000000..489dd86 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/models/book.models.dart @@ -0,0 +1,7 @@ +class Book { + final String id; + final String title; + final String author; + + const Book({required this.id, required this.title, required this.author}); +} diff --git a/examples/bottom_navigation_complex/lib/routers/app.router.dart b/examples/bottom_navigation_complex/lib/routers/app.router.dart new file mode 100644 index 0000000..bfff4b0 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/app.router.dart @@ -0,0 +1,39 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/routers/locations/article.location.dart'; +import 'package:bottom_navigation_complex/routers/locations/book.location.dart'; +import 'package:bottom_navigation_complex/routers/locations/layout.locations.dart'; +import 'package:bottom_navigation_complex/routers/locations/settings.locations.dart'; + +final BeamerDelegate routerDelegate = BeamerDelegate( + setBrowserTabTitle: false, + initialPath: '/Books', + routeListener: (_, delegate) { + print("${'=' * 15} History ${'=' * 15}"); + for (var beamHistory in delegate.beamingHistory) { + for (var history in beamHistory.history) { + print('BeamLocation: ${beamHistory.runtimeType} \t path: ${history.routeInformation.location}'); + } + } + }, + locationBuilder: BeamerLocationBuilder( + beamLocations: [ + LayoutLocation(), + BookLocation(), + ArticleLocation(), + SettingsLocation(), + ], + ), +); + +String? get previousLocation { + BeamLocation lastLocation = routerDelegate.beamingHistory.last; + if (lastLocation.history.length > 1) { + return lastLocation.history[lastLocation.history.length - 2].routeInformation.location; + } + + if (routerDelegate.beamingHistory.length > 1) { + return routerDelegate.beamingHistory[routerDelegate.beamingHistory.length - 2].history.last.routeInformation.location; + } + + return null; +} diff --git a/examples/bottom_navigation_complex/lib/routers/articles.router.dart b/examples/bottom_navigation_complex/lib/routers/articles.router.dart new file mode 100644 index 0000000..80f4e6f --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/articles.router.dart @@ -0,0 +1,21 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/screens/article_details.screen.dart'; +import 'package:bottom_navigation_complex/screens/articles.screen.dart'; +import 'package:flutter/foundation.dart'; +import 'package:bottom_navigation_complex/routers/app.router.dart'; + +final BeamerDelegate articlesRouterDelegate = BeamerDelegate( + setBrowserTabTitle: false, + initialPath: '/Articles', + clearBeamingHistoryOn: {'/Articles'}, + locationBuilder: RoutesLocationBuilder( + routes: { + '/Articles': (context, state, data) => const BeamPage(key: ValueKey('articles'), child: ArticlesScreen()), + '/Articles/:articleID': (context, state, data) => BeamPage( + key: ValueKey('articles-${state.pathParameters["articleID"]}'), + child: ArticleDetailsScreen(articleID: state.pathParameters["articleID"]!), + popToNamed: previousLocation, + ), + }, + ), +); diff --git a/examples/bottom_navigation_complex/lib/routers/books.router.dart b/examples/bottom_navigation_complex/lib/routers/books.router.dart new file mode 100644 index 0000000..dd1d588 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/books.router.dart @@ -0,0 +1,21 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/routers/app.router.dart'; +import 'package:bottom_navigation_complex/screens/book_details.screen.dart'; +import 'package:bottom_navigation_complex/screens/books.screen.dart'; +import 'package:flutter/foundation.dart'; + +final BeamerDelegate booksRouterDelegate = BeamerDelegate( + setBrowserTabTitle: false, + initialPath: '/Books', + clearBeamingHistoryOn: {'/Books'}, + locationBuilder: RoutesLocationBuilder( + routes: { + '/Books': (context, state, data) => const BeamPage(key: ValueKey('books'), child: Books()), + '/Books/:bookID': (context, state, data) => BeamPage( + key: ValueKey('books-${state.pathParameters["bookID"]}'), + child: BookDetailsScreen(bookID: state.pathParameters["bookID"]!), + popToNamed: previousLocation, + ), + }, + ), +); diff --git a/examples/bottom_navigation_complex/lib/routers/locations/article.location.dart b/examples/bottom_navigation_complex/lib/routers/locations/article.location.dart new file mode 100644 index 0000000..80d0615 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/locations/article.location.dart @@ -0,0 +1,21 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/routers/locations/layout.locations.dart'; +import 'package:bottom_navigation_complex/screens/article_details.screen.dart'; +import 'package:flutter/widgets.dart'; +import 'package:bottom_navigation_complex/routers/app.router.dart'; + +class ArticleLocation extends BeamLocation { + @override + List get pathPatterns => ['/Article/:articleID']; + + @override + List buildPages(BuildContext context, BeamState state) => [ + ...LayoutLocation().buildPages(context, state), + if (state.pathParameters.containsKey('articleID')) + BeamPage( + key: ValueKey('article-details-${state.pathParameters["articleID"]}'), + child: ArticleDetailsScreen(articleID: state.pathParameters["articleID"]!), + popToNamed: previousLocation, + ) + ]; +} diff --git a/examples/bottom_navigation_complex/lib/routers/locations/book.location.dart b/examples/bottom_navigation_complex/lib/routers/locations/book.location.dart new file mode 100644 index 0000000..772b2ad --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/locations/book.location.dart @@ -0,0 +1,21 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/routers/locations/layout.locations.dart'; +import 'package:bottom_navigation_complex/screens/book_details.screen.dart'; +import 'package:flutter/widgets.dart'; +import 'package:bottom_navigation_complex/routers/app.router.dart'; + +class BookLocation extends BeamLocation { + @override + List get pathPatterns => ['/Book/:bookID']; + + @override + List buildPages(BuildContext context, BeamState state) => [ + ...LayoutLocation().buildPages(context, state), + if (state.pathParameters.containsKey('bookID')) + BeamPage( + key: ValueKey('book-details-${state.pathParameters["bookID"]}'), + child: BookDetailsScreen(bookID: state.pathParameters["bookID"]!), + popToNamed: previousLocation, + ) + ]; +} diff --git a/examples/bottom_navigation_complex/lib/routers/locations/layout.locations.dart b/examples/bottom_navigation_complex/lib/routers/locations/layout.locations.dart new file mode 100644 index 0000000..5c920a4 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/locations/layout.locations.dart @@ -0,0 +1,13 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/screens/layout.screen.dart'; +import 'package:flutter/widgets.dart'; + +class LayoutLocation extends BeamLocation { + @override + List get pathPatterns => ['/Books', '/Books/*', '/Articles', '/Articles/*']; + + @override + List buildPages(BuildContext context, BeamState state) => [ + const BeamPage(key: ValueKey('layout'), child: LayoutScreen()), + ]; +} diff --git a/examples/bottom_navigation_complex/lib/routers/locations/settings.locations.dart b/examples/bottom_navigation_complex/lib/routers/locations/settings.locations.dart new file mode 100644 index 0000000..ce86735 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/routers/locations/settings.locations.dart @@ -0,0 +1,16 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/routers/app.router.dart'; +import 'package:bottom_navigation_complex/routers/locations/layout.locations.dart'; +import 'package:bottom_navigation_complex/screens/settings.screen.dart'; +import 'package:flutter/widgets.dart'; + +class SettingsLocation extends BeamLocation { + @override + List get pathPatterns => ['/Settings']; + + @override + List buildPages(BuildContext context, BeamState state) => [ + ...LayoutLocation().buildPages(context, state), + BeamPage(key: const ValueKey('settings'), child: const SettingsScreen(), popToNamed: previousLocation), + ]; +} diff --git a/examples/bottom_navigation_complex/lib/screens/article_details.screen.dart b/examples/bottom_navigation_complex/lib/screens/article_details.screen.dart new file mode 100644 index 0000000..dcb6826 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/article_details.screen.dart @@ -0,0 +1,50 @@ +import 'package:beamer/beamer.dart'; +import 'package:bottom_navigation_complex/data/data.dart'; +import 'package:bottom_navigation_complex/models/article.models.dart'; +import 'package:flutter/material.dart'; + +class ArticleDetailsScreen extends StatelessWidget { + final String articleID; + late final Article article = articles.firstWhere((article) => article.id == articleID); + + ArticleDetailsScreen({super.key, required this.articleID}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Article ${article.title}'), + actions: [ + IconButton( + icon: const Icon(Icons.open_in_new), + onPressed: () => Beamer.of(context).root.beamToNamed('/Article/${article.id}'), + ) + ], + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Text('Other articles:', style: Theme.of(context).textTheme.titleLarge), + ), + ...articles + .where((article) => article.id != articleID) + .map( + (article) => ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text(article.id), + ), + title: Text(article.title), + subtitle: Text(article.seller), + onTap: () => context.beamToNamed('/Articles/${article.id}'), + onLongPress: () => Beamer.of(context).root.beamToNamed('/Article/${article.id}'), + ), + ) + .toList(), + ], + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/articles.screen.dart b/examples/bottom_navigation_complex/lib/screens/articles.screen.dart new file mode 100644 index 0000000..268bf29 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/articles.screen.dart @@ -0,0 +1,42 @@ +import 'package:bottom_navigation_complex/data/data.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +class ArticlesScreen extends StatefulWidget { + const ArticlesScreen({super.key}); + + @override + State createState() => _ArticlesScreenState(); +} + +class _ArticlesScreenState extends State { + int counter = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Articles')), + body: ListView( + children: articles + .map( + (article) => ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text(article.id), + ), + title: Text(article.title), + subtitle: Text(article.seller), + onTap: () => context.beamToNamed('/Articles/${article.id}'), + onLongPress: () => Beamer.of(context).root.beamToNamed('/Article/${article.id}'), + ), + ) + .toList(), + ), + floatingActionButton: FloatingActionButton( + heroTag: null, + child: Text('$counter'), + onPressed: () => setState(() => counter++), + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/book_details.screen.dart b/examples/bottom_navigation_complex/lib/screens/book_details.screen.dart new file mode 100644 index 0000000..ffeb4b4 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/book_details.screen.dart @@ -0,0 +1,50 @@ +import 'package:bottom_navigation_complex/data/data.dart'; +import 'package:bottom_navigation_complex/models/book.models.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +class BookDetailsScreen extends StatelessWidget { + final String bookID; + late final Book book = books.firstWhere((book) => book.id == bookID); + + BookDetailsScreen({super.key, required this.bookID}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Book ${book.title}'), + actions: [ + IconButton( + icon: const Icon(Icons.open_in_new), + onPressed: () => Beamer.of(context).root.beamToNamed('/Book/${book.id}'), + ) + ], + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), + child: Text('Other books:', style: Theme.of(context).textTheme.titleLarge), + ), + ...books + .where((book) => book.id != bookID) + .map( + (book) => ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text(book.id), + ), + title: Text(book.title), + subtitle: Text(book.author), + onTap: () => context.beamToNamed('/Books/${book.id}'), + onLongPress: () => Beamer.of(context).root.beamToNamed('/Book/${book.id}'), + ), + ) + .toList(), + ], + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/books.screen.dart b/examples/bottom_navigation_complex/lib/screens/books.screen.dart new file mode 100644 index 0000000..2e8450d --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/books.screen.dart @@ -0,0 +1,60 @@ +import 'package:bottom_navigation_complex/data/data.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +class Books extends StatefulWidget { + const Books({super.key}); + + @override + State createState() => _BooksState(); +} + +class _BooksState extends State { + int counter = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Books')), + body: ListView( + children: books + .map( + (book) => ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text(book.id), + ), + title: Text(book.title), + subtitle: Text("Book - ${book.author}"), + onTap: () => context.beamToNamed('/Books/${book.id}'), + onLongPress: () => Beamer.of(context).root.beamToNamed('/Book/${book.id}'), + ), + ) + .toList() + ..add(ListTile( + title: const Text('Articles'), + onTap: () => Beamer.of(context).root.beamToNamed("/Articles"), + )) + ..addAll(articles + .map( + (article) => ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text(article.id), + ), + title: Text(article.title), + subtitle: Text("Article - ${article.seller}"), + onTap: () => context.beamToNamed('/Articles/${article.id}'), + onLongPress: () => Beamer.of(context).root.beamToNamed('/Article/${article.id}'), + ), + ) + .toList()), + ), + floatingActionButton: FloatingActionButton( + heroTag: null, + child: Text('$counter'), + onPressed: () => setState(() => counter++), + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/layout.screen.dart b/examples/bottom_navigation_complex/lib/screens/layout.screen.dart new file mode 100644 index 0000000..c2f8e44 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/layout.screen.dart @@ -0,0 +1,74 @@ +import 'package:bottom_navigation_complex/routers/books.router.dart'; +import 'package:bottom_navigation_complex/routers/articles.router.dart'; +import 'package:lazy_load_indexed_stack/lazy_load_indexed_stack.dart'; +import 'package:flutter/material.dart'; +import 'package:beamer/beamer.dart'; + +class LayoutScreen extends StatefulWidget { + const LayoutScreen({super.key}); + + @override + State createState() => _LayoutScreenState(); +} + +class _LayoutScreenState extends State { + final List routes = ['/Books', '/Articles']; + final List routerDelegates = [booksRouterDelegate, articlesRouterDelegate]; + late List children = List.generate( + routes.length, + (index) => Beamer( + backButtonDispatcher: BeamerBackButtonDispatcher(delegate: routerDelegates[index]), + routerDelegate: routerDelegates[index], + )); + late int currentIndex = -1; + + @override + void didChangeDependencies() { + final uriString = Beamer.of(context).configuration.location!; + int index = routes.indexWhere((route) => uriString.startsWith(route)); + _updateCurrentIndex(index != -1 ? index : 0); + super.didChangeDependencies(); + } + + void _updateCurrentIndex(int index) { + // if the index is not the same, then we need to update the route + if (currentIndex != index) { + // Fix: After build complete of the new screen (lazy), update the route to trigger the history + if (currentIndex != -1) { + WidgetsBinding.instance.addPostFrameCallback((_) => children[index].routerDelegate.update(rebuild: false)); + } + setState(() => currentIndex = index); + } else { + // If the index is the same, reset the Beamer to the initial state. + children[index].routerDelegate.beamToReplacementNamed(routes[index]); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Layout'), + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () => context.beamToNamed('/Settings'), + ) + ], + ), + body: LazyLoadIndexedStack(index: currentIndex, children: children), + bottomNavigationBar: BottomNavigationBar( + items: const [ + BottomNavigationBarItem(label: 'Books', icon: Icon(Icons.book)), + BottomNavigationBarItem(label: 'Articles', icon: Icon(Icons.article)), + ], + currentIndex: currentIndex, + onTap: _updateCurrentIndex, + selectedItemColor: Theme.of(context).primaryColor, + unselectedItemColor: Colors.black, + type: BottomNavigationBarType.fixed, + showUnselectedLabels: true, + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/settings.screen.dart b/examples/bottom_navigation_complex/lib/screens/settings.screen.dart new file mode 100644 index 0000000..3a207bc --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/settings.screen.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class SettingsScreen extends StatelessWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Settings')), + ); + } +} diff --git a/examples/bottom_navigation_complex/lib/screens/splash.screen.dart b/examples/bottom_navigation_complex/lib/screens/splash.screen.dart new file mode 100644 index 0000000..d593221 --- /dev/null +++ b/examples/bottom_navigation_complex/lib/screens/splash.screen.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class SplashScreen extends StatefulWidget { + final Widget? screen; + + const SplashScreen({Key? key, this.screen}) : super(key: key); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + bool loading = true; + + @override + void initState() { + // Simulate fetching data from server or async load of preferences + Future.delayed(const Duration(seconds: 2)).then((_) => setState(() => loading = false)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (!loading && widget.screen != null) return widget.screen!; + return Scaffold( + backgroundColor: Theme.of(context).primaryColor, + body: const Center( + child: Text( + 'Splash', + style: TextStyle(color: Colors.white, fontSize: 30), + ), + ), + ); + } +} diff --git a/examples/bottom_navigation_complex/pubspec.yaml b/examples/bottom_navigation_complex/pubspec.yaml new file mode 100644 index 0000000..d786fe8 --- /dev/null +++ b/examples/bottom_navigation_complex/pubspec.yaml @@ -0,0 +1,27 @@ +name: bottom_navigation_complex +description: Complex bottom navigation example app. + +publish_to: 'none' + +version: 1.0.0+1 + +environment: + sdk: ">=2.17.5 <3.0.0" + +dependencies: + flutter: + sdk: flutter + beamer: + path: ../../package + + cupertino_icons: ^1.0.3 + lazy_load_indexed_stack: ^0.1.4 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true \ No newline at end of file