Replace navigation_and_routing with a new sample (#832)

* move snippets into old_snippets directory

* add new navigation_and_routing sample

* add copyright headers

* Apply #827 to old_snippets/ directory and upgrade them to null safety

* Code review comments

- Move Guard class into parser.dart
- Move usage of guards from Delegate to RouteInformationParser
- Rename delegate to SimpleRouterDelegate

* clean up imports

* refactor settings screen, fix bug

* avoid conflicting paths /books/new and /books/1 - rename to book/1

* dispose fields in _BookstoreState class

* remove /books path

This was causing problems

* add comment

* Change BookstoreAuthScope and BookstoreAuthScope to InheritedNotifier

* fix warnings

* Make the initial route configurable, set to '/signin'

* Enable deep linking

https://flutter.dev/docs/development/ui/navigation/deep-linking

* use path URL strategy on the web.

* remove TODO, add comment
pull/846/head
John Ryan 4 years ago committed by GitHub
parent 8573269c03
commit ae3c4e3c47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,3 +39,8 @@ app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: stable
project_type: app

@ -1,47 +1,5 @@
# Navigation and Routing sample code
# bookstore
Sample code for [Learning Flutter's New Navigation and Routing System][article],
an article explaining the Router widget and its associated classes.
This sample shows how to set up a Router using a custom RouterDelegate and
RouteInformationParser.
## Samples
**Navigator samples**
* [navigator/anonymous_routes.dart](lib/navigator/anonymous_routes.dart) -
Shows how to use a Navigator to push and pop anonymous routes (e.g.
MaterialPageRoute)
* [navigator/named_routes.dart](lib/navigator/named_routes.dart) - Shows
how to use define named routes via the `routes` parameter on MaterialApp, and
navigate using Navigator.pushNamed
* [navigator/on_generate_route.dart](lib/navigator/on_generate_route.dart) -
Shows how to handle arbitrary named routes using the `onGenerateRoute`
callback defined in the `MaterialApp` constructor.
**Router samples**
* [router/pages.dart](lib/router/pages.dart) - Shows how to define a list of
[Page] objects on Navigator declaratively.
* [router/router.dart](lib/router/router.dart) - Full sample that shows a custom
RouteInformationParser and RouterDelegate parsing named routes and
declaratively building the stack of pages for the Navigator.
**Advanced**
* [router_advanced/nested_router.dart](lib/router_advanced/nested_router.dart) -
Shows two [RouterDelegate], one nested within the other. A
[BottomNavigationBar] can be used to select the route of the outer
RouterDelegate, and additional routes can be pushed onto the inner
RouterDelegate / Navigator.
* [router_advanced/transition_delegate.dart](lib/router_advanced/transition_delegate.dart)
Shows how a custom TransitionDelegate can be used to customize when
transition animations are shown.
## Running
Each file in this project is an entrypoint. To run, specify the filename of
the sample:
```bash
flutter run lib/router/router.dart
```
[article]: https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade

@ -0,0 +1,19 @@
include: package:flutter_lints/flutter.yaml
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
linter:
rules:
avoid_types_on_closure_parameters: true
avoid_void_async: true
cancel_subscriptions: true
close_sinks: true
directives_ordering: true
package_api_docs: true
package_prefixed_library_names: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true

@ -26,21 +26,17 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.navigation_and_routing"
applicationId "dev.flutter.bookstore"
minSdkVersion 16
targetSdkVersion 29
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.navigation_and_routing">
package="dev.flutter.bookstore">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

@ -1,13 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.navigation_and_routing">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="navigation_and_routing"
package="dev.flutter.bookstore">
<application
android:label="bookstore"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
@ -37,6 +31,16 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Deep linking -->
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="flutterbooksample.com" />
<data android:scheme="https" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

@ -1,4 +1,4 @@
package com.example.navigation_and_routing
package dev.flutter.bookstore
import io.flutter.embedding.android.FlutterActivity

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
@ -12,7 +12,7 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.navigation_and_routing">
package="dev.flutter.bookstore">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -21,8 +21,6 @@ allprojects {
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

@ -18,6 +18,7 @@ Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

@ -0,0 +1,22 @@
PODS:
- Flutter (1.0.0)
- url_launcher (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
SPEC CHECKSUMS:
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
COCOAPODS: 1.10.1

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
163107DCA98CF5408A42DD8A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AE3A8CEB94AABB517354DA1 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@ -29,12 +30,16 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0AE3A8CEB94AABB517354DA1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
49D7EF814BF5A0D3E798ADAE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
71C70971C603166B5757B8AC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9682F7751FBA2903F6CCD179 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -49,12 +54,24 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
163107DCA98CF5408A42DD8A /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7B81C16A03A3AB694F08AD07 /* Pods */ = {
isa = PBXGroup;
children = (
71C70971C603166B5757B8AC /* Pods-Runner.debug.xcconfig */,
9682F7751FBA2903F6CCD179 /* Pods-Runner.release.xcconfig */,
49D7EF814BF5A0D3E798ADAE /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -72,6 +89,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
7B81C16A03A3AB694F08AD07 /* Pods */,
DCFFB20C3B2B4F4DCC6CF244 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -98,6 +117,14 @@
path = Runner;
sourceTree = "<group>";
};
DCFFB20C3B2B4F4DCC6CF244 /* Frameworks */ = {
isa = PBXGroup;
children = (
0AE3A8CEB94AABB517354DA1 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -105,12 +132,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
E223DFBBAC6A945FFD0847A4 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
766B88730AAF26F1ADAF797C /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -183,6 +212,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
766B88730AAF26F1ADAF797C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -197,6 +243,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
E223DFBBAC6A945FFD0847A4 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -289,17 +357,9 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.navigationAndRouting;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.bookstore;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -421,17 +481,9 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.navigationAndRouting;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.bookstore;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -448,17 +500,9 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.navigationAndRouting;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.bookstore;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -11,7 +11,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>navigation_and_routing</string>
<string>bookstore</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@ -41,5 +41,20 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>flutterbooksample.com</string>
<key>CFBundleURLSchemes</key>
<array>
<string>customscheme</string>
</array>
</dict>
</array>
</dict>
</plist>

@ -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 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'src/app.dart';
void main() {
// Use package:url_strategy until this pull request is released:
// https://github.com/flutter/flutter/pull/77103
setPathUrlStrategy();
runApp(const Bookstore());
}

@ -0,0 +1,101 @@
// 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 'auth.dart';
import 'data.dart';
import 'routing.dart';
import 'screens/navigator.dart';
import 'widgets/library_scope.dart';
class Bookstore extends StatefulWidget {
const Bookstore({Key? key}) : super(key: key);
@override
_BookstoreState createState() => _BookstoreState();
}
class _BookstoreState extends State<Bookstore> {
final auth = BookstoreAuth();
late final BookstoreRouteGuard guard;
late final RouteState routeState;
late final SimpleRouterDelegate routerDelegate;
late final TemplateRouteParser routeParser;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final library = Library()
..addBook('Left Hand of Darkness', 'Ursula K. Le Guin', true, true)
..addBook('Too Like the Lightning', 'Ada Palmer', false, true)
..addBook('Kindred', 'Octavia E. Butler', true, false)
..addBook('The Lathe of Heaven', 'Ursula K. Le Guin', false, false);
@override
void initState() {
guard = BookstoreRouteGuard(auth: auth);
/// Configure the parser with all of the app's allowed path templates.
routeParser = TemplateRouteParser(
[
'/signin',
'/authors',
'/settings',
'/books/new',
'/books/all',
'/books/popular',
'/book/:bookId',
'/author/:authorId',
],
guard: guard,
initialRoute: '/signin',
);
routeState = RouteState(routeParser);
routerDelegate = SimpleRouterDelegate(
routeState: routeState,
navigatorKey: navigatorKey,
builder: (context) => BookstoreNavigator(
navigatorKey: navigatorKey,
auth: auth,
),
);
// Listen for when the user logs out and display the signin screen.
auth.addListener(_handleAuthStateChanged);
super.initState();
}
@override
Widget build(BuildContext context) {
return RouteStateScope(
notifier: routeState,
child: BookstoreAuthScope(
notifier: auth,
child: LibraryScope(
library: library,
child: MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: routeParser,
),
),
),
);
}
Future<void> _handleAuthStateChanged() async {
if (!auth.signedIn) {
routeState.go('/signin');
}
}
@override
void dispose() {
auth.removeListener(_handleAuthStateChanged);
routeState.dispose();
routerDelegate.dispose();
super.dispose();
}
}

@ -0,0 +1,2 @@
export 'auth/auth.dart';
export 'auth/auth_guard.dart';

@ -0,0 +1,53 @@
// 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/foundation.dart';
import 'package:flutter/widgets.dart';
/// A mock authentication service
class BookstoreAuth extends ChangeNotifier {
bool _signedIn = false;
bool get signedIn {
return _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) {
return other is BookstoreAuth && other._signedIn == _signedIn;
}
@override
int get hashCode => _signedIn.hashCode;
}
class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
const BookstoreAuthScope({
required BookstoreAuth notifier,
required Widget child,
Key? key,
}) : super(key: key, notifier: notifier, child: child);
static BookstoreAuth? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()
?.notifier;
}
}

@ -0,0 +1,32 @@
// 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 '../routing.dart';
import 'auth.dart';
/// An implementation of [RouteGuard] that redirects to /signIn
class BookstoreRouteGuard implements RouteGuard<ParsedRoute> {
BookstoreAuth auth;
BookstoreRouteGuard({
required this.auth,
});
/// Redirect to /signin if the user isn't signed in.
@override
Future<ParsedRoute> redirect(ParsedRoute from) async {
final signedIn = auth.signedIn;
final signInRoute = ParsedRoute('/signin', '/signin', {}, {});
// Go to /signin if the user is not signed in
if (!signedIn && from != signInRoute) {
return signInRoute;
}
// Go to /books if the user is signed in and tries to go to /signin.
else if (signedIn && from == signInRoute) {
return ParsedRoute('/books/popular', '/books/popular', {}, {});
}
return from;
}
}

@ -0,0 +1,3 @@
export 'data/author.dart';
export 'data/book.dart';
export 'data/library.dart';

@ -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 List<Book> books;
Author(this.id, this.name, this.books);
}

@ -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;
late final Author author;
final bool isPopular;
final bool isNew;
Book(this.id, this.title, this.isPopular, this.isNew);
}

@ -0,0 +1,41 @@
// 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:collection/collection.dart';
import 'author.dart';
import 'book.dart';
class Library {
final List<Book> allBooks = [];
final List<Author> allAuthors = [];
void addBook(String title, String authorName, bool isPopular, bool isNew) {
var author =
allAuthors.firstWhereOrNull((author) => author.name == authorName);
var book = Book(allBooks.length, title, isPopular, isNew);
if (author == null) {
author = Author(allAuthors.length, authorName, [book]);
allAuthors.add(author);
} else {
author.books.add(book);
}
book.author = author;
allBooks.add(book);
}
List<Book> get popularBooks {
return [
...allBooks.where((book) => book.isPopular),
];
}
List<Book> get newBooks {
return [
...allBooks.where((book) => book.isNew),
];
}
}

@ -0,0 +1,4 @@
export 'routing/delegate.dart';
export 'routing/parsed_route.dart';
export 'routing/parser.dart';
export 'routing/route_state.dart';

@ -0,0 +1,52 @@
// 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 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'parsed_route.dart';
import 'route_state.dart';
class SimpleRouterDelegate extends RouterDelegate<ParsedRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<ParsedRoute> {
final RouteState routeState;
final WidgetBuilder builder;
@override
final GlobalKey<NavigatorState> navigatorKey;
SimpleRouterDelegate({
required this.routeState,
required this.builder,
required this.navigatorKey,
// ignore: prefer_initializing_formals
}) {
routeState.addListener(notifyListeners);
}
@override
Widget build(BuildContext context) {
return builder(context);
}
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
routeState.route = configuration;
return SynchronousFuture(null);
}
@override
ParsedRoute get currentConfiguration {
return routeState.route;
}
@override
void dispose() {
routeState.removeListener(notifyListeners);
routeState.dispose();
super.dispose();
}
}

@ -0,0 +1,45 @@
// 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:collection/collection.dart';
import 'package:quiver/core.dart';
import 'parser.dart';
/// A route path that has been parsed by [TemplateRouteParser].
class ParsedRoute {
final String path;
final String pathTemplate;
final Map<String, String> parameters;
final Map<String, String> queryParameters;
static const _mapEquality = MapEquality<String, String>();
ParsedRoute(
this.path, this.pathTemplate, this.parameters, this.queryParameters);
@override
bool operator ==(Object other) {
return other is ParsedRoute &&
other.pathTemplate == pathTemplate &&
other.path == path &&
_mapEquality.equals(parameters, other.parameters) &&
_mapEquality.equals(queryParameters, other.queryParameters);
}
@override
int get hashCode => hash4(
path,
pathTemplate,
_mapEquality.hash(parameters),
_mapEquality.hash(queryParameters),
);
@override
String toString() => '<ParsedRoute '
'template: $pathTemplate '
'path: $path '
'parameters: $parameters '
'query parameters: $queryParameters>';
}

@ -0,0 +1,68 @@
// 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';
import 'package:path_to_regexp/path_to_regexp.dart';
import 'parsed_route.dart';
abstract class RouteGuard<T> {
Future<T> redirect(T from);
}
/// Parses the URI path into a [ParsedRoute].
class TemplateRouteParser extends RouteInformationParser<ParsedRoute> {
final List<String> _pathTemplates = [];
RouteGuard<ParsedRoute>? guard;
final ParsedRoute initialRoute;
TemplateRouteParser(List<String> pathTemplates,
{String? initialRoute = '/', this.guard})
: initialRoute =
ParsedRoute(initialRoute ?? '/', initialRoute ?? '/', {}, {}) {
for (var template in pathTemplates) {
_addRoute(template);
}
}
void _addRoute(String pathTemplate) {
_pathTemplates.add(pathTemplate);
}
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
return await _parse(routeInformation);
}
Future<ParsedRoute> _parse(RouteInformation routeInformation) async {
final path = routeInformation.location!;
final queryParams = Uri.parse(path).queryParameters;
var parsedRoute = initialRoute;
for (var pathTemplate in _pathTemplates) {
final parameters = <String>[];
var pathRegExp = pathToRegExp(pathTemplate, parameters: parameters);
if (pathRegExp.hasMatch(path)) {
final match = pathRegExp.matchAsPrefix(path);
if (match == null) continue;
final params = extract(parameters, match);
parsedRoute = ParsedRoute(path, pathTemplate, params, queryParams);
}
}
// Redirect if a guard is present
var guard = this.guard;
if (guard != null) {
return guard.redirect(parsedRoute);
}
return parsedRoute;
}
@override
RouteInformation restoreRouteInformation(ParsedRoute configuration) {
return RouteInformation(location: configuration.path);
}
}

@ -0,0 +1,50 @@
// 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/foundation.dart';
import 'package:flutter/widgets.dart';
import 'parsed_route.dart';
import 'parser.dart';
/// The current route state. To change the current route, call obtain the state using
/// `RouteState.of(context)` and call `go()`:
///
/// ```
/// RouteState.of(context).go('/book/2');
/// ```
class RouteState extends ChangeNotifier {
TemplateRouteParser parser;
ParsedRoute _route;
RouteState(this.parser)
: _route = parser.initialRoute;
ParsedRoute get route => _route;
set route(ParsedRoute route) {
_route = route;
notifyListeners();
}
Future<void> go(String route) async {
this.route =
await parser.parseRouteInformation(RouteInformation(location: route));
}
}
/// Provides the current [RouteState] to descendent widgets in the tree.
class RouteStateScope extends InheritedNotifier<RouteState> {
const RouteStateScope({
required RouteState notifier,
required Widget child,
Key? key,
}) : super(key: key, notifier: notifier, child: child);
static RouteState? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<RouteStateScope>()
?.notifier;
}
}

@ -0,0 +1,49 @@
// 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';
import 'book_details.dart';
class AuthorDetailsScreen extends StatelessWidget {
final Author author;
const AuthorDetailsScreen({
Key? key,
required this.author,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(author.name),
),
body: Center(
child: Column(
children: [
Expanded(
child: BookList(
books: author.books,
onTap: (book) {
Navigator.of(context).push<dynamic>(
MaterialPageRoute<dynamic>(
builder: (context) {
return BookDetailsScreen(
book: book,
);
},
),
);
},
),
),
],
),
),
);
}
}

@ -0,0 +1,30 @@
// 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 '../routing.dart';
import '../widgets/author_list.dart';
import '../widgets/library_scope.dart';
class AuthorsScreen extends StatelessWidget {
final String title = "Authors";
const AuthorsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: AuthorList(
authors: LibraryScope.of(context).allAuthors,
onTap: (author) {
RouteStateScope.of(context)!.go('/author/${author.id}');
},
),
);
}
}

@ -0,0 +1,75 @@
// 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:url_launcher/link.dart';
import '../data.dart';
import 'author_details.dart';
class BookDetailsScreen extends StatelessWidget {
final Book? book;
const BookDetailsScreen({
Key? key,
this.book,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (book == null) {
return const Scaffold(
body: Center(
child: Text('No book with found.'),
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(book!.title),
),
body: Center(
child: Column(
children: [
Text(
book!.title,
style: Theme
.of(context)
.textTheme
.headline4,
),
Text(
book!.author.name,
style: Theme
.of(context)
.textTheme
.subtitle1,
),
TextButton(
child: const Text('View author (Push)'),
onPressed: () {
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (context) {
return AuthorDetailsScreen(author: book!.author);
},
),
);
},
),
Link(
uri: Uri.parse('/author/${book!.author.id}'),
builder: (context, followLink) {
return TextButton(
child: const Text('View author (Link)'),
onPressed: followLink,
);
},
),
],
),
),
);
}
}

@ -0,0 +1,130 @@
// 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 '../routing.dart';
import '../widgets/book_list.dart';
import '../widgets/library_scope.dart';
class BooksScreen extends StatefulWidget {
final ParsedRoute currentRoute;
const BooksScreen({
Key? key,
required this.currentRoute,
}) : super(key: key);
@override
_BooksScreenState 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 dispose() {
_tabController.removeListener(_handleTabIndexChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
final library = LibraryScope.of(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: TabBarView(
controller: _tabController,
children: [
BookList(
books: library.popularBooks,
onTap: _handleBookTapped,
),
BookList(
books: library.newBooks,
onTap: _handleBookTapped,
),
BookList(
books: library.allBooks,
onTap: _handleBookTapped,
),
],
),
);
}
String get title {
switch (_tabController.index) {
case 1:
return 'New';
case 2:
return 'All';
case 0:
default:
return 'Popular';
}
}
RouteState get routeState => RouteStateScope.of(context)!;
void _handleBookTapped(Book book) {
routeState.go('/book/${book.id}');
}
void _handleTabIndexChanged() {
switch (_tabController.index) {
case 1:
routeState.go('/books/new');
break;
case 2:
routeState.go('/books/all');
break;
case 0:
default:
routeState.go('/books/popular');
break;
}
}
@override
void didUpdateWidget(BooksScreen oldWidget) {
var newPath = routeState.route.pathTemplate;
if (newPath.startsWith('/books/popular')) {
_tabController.index = 0;
} else if (newPath.startsWith('/books/new')) {
_tabController.index = 1;
} else if (newPath == '/books/all') {
_tabController.index = 2;
}
super.didUpdateWidget(oldWidget);
}
}

@ -0,0 +1,111 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import '../auth.dart';
import '../data.dart';
import '../routing.dart';
import '../screens/sign_in.dart';
import '../widgets/fade_transition_page.dart';
import '../widgets/library_scope.dart';
import 'author_details.dart';
import 'book_details.dart';
import 'scaffold.dart';
/// Builds the top-level navigator for the app. The pages to display are based
/// on the [routeState] that was parsed by the TemplateRouteParser.
class BookstoreNavigator extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
final BookstoreAuth auth;
const BookstoreNavigator({
required this.auth,
required this.navigatorKey,
Key? key,
}) : super(key: key);
@override
_BookstoreNavigatorState createState() => _BookstoreNavigatorState();
}
class _BookstoreNavigatorState extends State<BookstoreNavigator> {
final scaffoldKey = const ValueKey<String>('App scaffold');
final bookDetailsKey = const ValueKey<String>('Book details screen');
final authorDetailsKey = const ValueKey<String>('Author details screen');
@override
Widget build(BuildContext context) {
final routeState = RouteStateScope.of(context)!;
final pathTemplate = routeState.route.pathTemplate;
final library = LibraryScope.of(context);
Book? book;
if (pathTemplate == '/book/:bookId') {
book = library.allBooks.firstWhereOrNull(
(b) => b.id.toString() == routeState.route.parameters['bookId']);
}
Author? author;
if (pathTemplate == '/author/:authorId') {
author = library.allAuthors.firstWhereOrNull(
(b) => b.id.toString() == routeState.route.parameters['authorId']);
}
return Navigator(
key: widget.navigatorKey,
onPopPage: (route, dynamic result) {
// When a page that is stacked on top of the scaffold is popped, display
// the /books or /authors tab in BookstoreScaffold.
if (route.settings is Page &&
(route.settings as Page).key == bookDetailsKey) {
routeState.go('/books/popular');
}
if (route.settings is Page &&
(route.settings as Page).key == authorDetailsKey) {
routeState.go('/authors');
}
return route.didPop(result);
},
pages: [
if (routeState.route.pathTemplate == '/signin')
// Display the sign in screen.
FadeTransitionPage<void>(
key: const ValueKey('Sign in'),
child: SignInScreen(
onSignIn: (credentials) async {
var signedIn = await widget.auth
.signIn(credentials.username, credentials.password);
if (signedIn) {
routeState.go('/books/popular');
}
},
),
)
else ...[
// Display the app
FadeTransitionPage<void>(
key: scaffoldKey,
child: const BookstoreScaffold(),
),
// Add an additional page to the stack if the user is viewing a book
// or an author
if (book != null)
MaterialPage<void>(
key: bookDetailsKey,
child: BookDetailsScreen(
book: book,
),
)
else if (author != null)
MaterialPage<void>(
key: authorDetailsKey,
child: AuthorDetailsScreen(
author: author,
),
),
],
],
);
}
}

@ -0,0 +1,54 @@
// 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 '../routing.dart';
import 'scaffold_body.dart';
class BookstoreScaffold extends StatelessWidget {
const BookstoreScaffold({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final routeState = RouteStateScope.of(context)!;
final selectedIndex = _getSelectedIndex(routeState.route.pathTemplate);
return Scaffold(
body: AdaptiveNavigationScaffold(
selectedIndex: selectedIndex,
body: const BookstoreScaffoldBody(),
onDestinationSelected: (idx) {
if (idx == 0) routeState.go('/books/popular');
if (idx == 1) routeState.go('/authors');
if (idx == 2) routeState.go('/settings');
},
destinations: const [
AdaptiveScaffoldDestination(
title: 'Books',
icon: Icons.book,
),
AdaptiveScaffoldDestination(
title: 'Authors',
icon: Icons.person,
),
AdaptiveScaffoldDestination(
title: 'Settings',
icon: Icons.settings,
),
],
),
);
}
int _getSelectedIndex(String pathTemplate) {
if (pathTemplate.startsWith('/books')) return 0;
if (pathTemplate == '/authors') return 1;
if (pathTemplate == '/settings') return 2;
return 0;
}
}

@ -0,0 +1,62 @@
// 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 '../routing.dart';
import '../screens/settings.dart';
import '../widgets/fade_transition_page.dart';
import 'authors.dart';
import 'books.dart';
/// Displays the contents of the body of [BookstoreScaffold]
class BookstoreScaffoldBody extends StatelessWidget {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
const BookstoreScaffoldBody({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
var currentRoute = RouteStateScope.of(context)!.route;
// A nested Router isn't necessary because the back button behavior doesn't
// need to be customized.
return Navigator(
key: navigatorKey,
onPopPage: (route, dynamic result) => route.didPop(result),
pages: [
if (currentRoute.pathTemplate.startsWith('/authors'))
const FadeTransitionPage<void>(
key: ValueKey('authors'),
child: AuthorsScreen(),
)
else if (currentRoute.pathTemplate.startsWith('/settings'))
const FadeTransitionPage<void>(
key: ValueKey('settings'),
child: SettingsScreen(),
)
else if (currentRoute.pathTemplate.startsWith('/books') ||
currentRoute.pathTemplate == '/')
FadeTransitionPage<void>(
key: const ValueKey('books'),
child: BooksScreen(currentRoute: currentRoute),
)
// Avoid building a Navigator with an empty `pages` list when the
// RouteState is set to an unexpected path, such as /signin.
//
// Since RouteStateScope is an InheritedNotifier, any change to the
// route will result in a call to this build method, even though this
// widget isn't built when those routes are active.
else
FadeTransitionPage<void>(
key: const ValueKey('empty'),
child: Container(),
),
],
);
}
}

@ -0,0 +1,103 @@
// 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:url_launcher/link.dart';
import '../auth/auth.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
@override
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
@override
Widget build(BuildContext context) {
return 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({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
...[
Text(
'Settings',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: () {
BookstoreAuthScope.of(context)!.signOut();
},
child: const Text('Sign out'),
),
Link(
uri: Uri.parse('/book/0'),
builder: (context, followLink) {
return TextButton(
child: const Text('Go directly to /book/0'),
onPressed: followLink,
);
},
),
Link(
uri: Uri.parse('/author/0'),
builder: (context, followLink) {
return TextButton(
child: const Text('Go directly to /author/0'),
onPressed: followLink,
);
},
),
].map((w) => Padding(padding: const EdgeInsets.all(8), child: w)),
TextButton(
onPressed: () => showDialog<String>(
context: context,
builder: (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'),
)
],
);
}
}

@ -0,0 +1,76 @@
// 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/cupertino.dart';
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,
Key? key,
}) : super(key: key);
@override
_SignInScreenState createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
String username = '';
String password = '';
@override
Widget build(BuildContext context) {
return 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.headline4),
TextField(
decoration: const InputDecoration(labelText: 'Username'),
onChanged: (v) {
setState(() {
username = v;
});
},
),
TextField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: (v) {
setState(() {
password = v;
});
},
),
Padding(
padding: const EdgeInsets.all(16),
child: TextButton(
onPressed: () async {
widget.onSignIn(Credentials(username, password));
},
child: const Text('Sign in'),
),
),
],
),
),
),
),
);
}
}

@ -0,0 +1,36 @@
// 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,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: authors.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
authors[index].name,
),
subtitle: Text(
'${authors[index].books.length} books',
),
onTap: onTap != null ? () => onTap!(authors[index]) : null,
);
},
);
}
}

@ -0,0 +1,36 @@
// 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,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
books[index].title,
),
subtitle: Text(
books[index].author.name,
),
onTap: onTap != null ? () => onTap!(books[index]) : null,
);
},
);
}
}

@ -0,0 +1,52 @@
// 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;
const FadeTransitionPage({LocalKey? key, required this.child})
: super(key: key);
@override
Route<T> createRoute(BuildContext context) {
return PageBasedFadeTransitionRoute<T>(this);
}
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
PageBasedFadeTransitionRoute(Page page)
: super(
settings: page,
);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@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) {
return child;
}
}

@ -0,0 +1,25 @@
// 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';
import '../data.dart';
class LibraryScope extends InheritedWidget {
final Library library;
const LibraryScope({
Key? key,
required this.library,
required Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(LibraryScope oldWidget) =>
library != oldWidget.library;
static Library of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<LibraryScope>()!.library;
}
}

@ -0,0 +1,6 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/xcuserdata/

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

@ -0,0 +1,40 @@
platform :osx, '10.11'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

@ -0,0 +1,572 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* bookstore.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "bookstore.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* bookstore.app */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* bookstore.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "bookstore.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "bookstore.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "bookstore.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "bookstore.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,9 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

@ -0,0 +1,14 @@
// Application-level settings for the Runner target.
//
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
// future. If not, the values below would default to using the project name when this becomes a
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = bookstore
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.bookstore
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2021 dev.flutter. All rights reserved.

@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Debug.xcconfig"
#include "Warnings.xcconfig"

@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Release.xcconfig"
#include "Warnings.xcconfig"

@ -0,0 +1,13 @@
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
GCC_WARN_UNDECLARED_SELECTOR = YES
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_PRAGMA_PACK = YES
CLANG_WARN_STRICT_PROTOTYPES = YES
CLANG_WARN_COMMA = YES
GCC_WARN_STRICT_SELECTOR_MATCH = YES
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
GCC_WARN_SHADOW = YES
CLANG_WARN_UNREACHABLE_CODE = YES

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

@ -0,0 +1,15 @@
import Cocoa
import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

@ -9,31 +9,35 @@ library anonymous_routes;
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
runApp(const Nav2App());
}
class Nav2App extends StatelessWidget {
const Nav2App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
return const MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextButton(
child: Text('View Details'),
child: const Text('View Details'),
onPressed: () {
Navigator.push(
Navigator.push<void>(
context,
MaterialPageRoute(builder: (context) {
return DetailScreen();
return const DetailScreen();
}),
);
},
@ -44,13 +48,15 @@ class HomeScreen extends StatelessWidget {
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextButton(
child: Text('Pop!'),
child: const Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},

@ -9,29 +9,33 @@ library named_routes;
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
runApp(const Nav2App());
}
class Nav2App extends StatelessWidget {
const Nav2App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextButton(
child: Text('View Details'),
child: const Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
@ -45,13 +49,15 @@ class HomeScreen extends StatelessWidget {
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextButton(
child: Text('Pop!'),
child: const Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},

@ -9,41 +9,48 @@ library on_generate_route;
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
runApp(const Nav2App());
}
class Nav2App extends StatelessWidget {
const Nav2App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// Handle '/'
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
return MaterialPageRoute<void>(
builder: (context) => const HomeScreen());
}
// Handle '/details/:id'
var uri = Uri.parse(settings.name);
var uri = Uri.parse(settings.name ?? '');
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
return MaterialPageRoute<void>(
builder: (context) => DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
return MaterialPageRoute<void>(
builder: (context) => const UnknownScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: TextButton(
child: Text('View Details'),
child: const Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
@ -59,9 +66,10 @@ class HomeScreen extends StatelessWidget {
class DetailScreen extends StatelessWidget {
final String id;
DetailScreen({
this.id,
});
const DetailScreen({
required this.id,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -73,7 +81,7 @@ class DetailScreen extends StatelessWidget {
children: [
Text('Viewing details for item $id'),
TextButton(
child: Text('Pop!'),
child: const Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
@ -86,11 +94,13 @@ class DetailScreen extends StatelessWidget {
}
class UnknownScreen extends StatelessWidget {
const UnknownScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
body: const Center(
child: Text('404!'),
),
);

@ -8,7 +8,7 @@ library nav2_pages;
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
runApp(const BooksApp());
}
class Book {
@ -19,12 +19,14 @@ class Book {
}
class BooksApp extends StatefulWidget {
const BooksApp({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
Book _selectedBook;
Book? _selectedBook;
final List<Book> books = [
Book('Left Hand of Darkness', 'Ursula K. Le Guin'),
@ -38,8 +40,8 @@ class _BooksAppState extends State<BooksApp> {
title: 'Books App',
home: Navigator(
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
MaterialPage<void>(
key: const ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
@ -47,7 +49,7 @@ class _BooksAppState extends State<BooksApp> {
),
if (_selectedBook != null) BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
onPopPage: (route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
@ -70,17 +72,18 @@ class _BooksAppState extends State<BooksApp> {
}
}
class BookDetailsPage extends Page {
final Book book;
class BookDetailsPage extends Page<void> {
final Book? book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
@override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
return MaterialPageRoute<void>(
settings: this,
builder: (BuildContext context) {
builder: (context) {
return BookDetailsScreen(book: book);
},
);
@ -91,10 +94,11 @@ class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
const BooksListScreen({
required this.books,
required this.onTapped,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -115,11 +119,12 @@ class BooksListScreen extends StatelessWidget {
}
class BookDetailsScreen extends StatelessWidget {
final Book book;
final Book? book;
BookDetailsScreen({
const BookDetailsScreen({
@required this.book,
});
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -131,8 +136,8 @@ class BookDetailsScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
Text(book!.title, style: Theme.of(context).textTheme.headline6),
Text(book!.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),

@ -9,7 +9,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
runApp(const BooksApp());
}
class Book {
@ -20,13 +20,15 @@ class Book {
}
class BooksApp extends StatefulWidget {
const BooksApp({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
final BookRouterDelegate _routerDelegate = BookRouterDelegate();
final BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
@ -44,9 +46,9 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation,
) {
final uri = Uri.parse(routeInformation.location);
final uri = Uri.parse(routeInformation.location ?? '');
// Handle '/'
if (uri.pathSegments.length == 0) {
if (uri.pathSegments.isEmpty) {
return SynchronousFuture(BookRoutePath.home());
}
@ -64,15 +66,15 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
RouteInformation? restoreRouteInformation(BookRoutePath configuration) {
if (configuration.isUnknown) {
return const RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
if (configuration.isHomePage) {
return const RouteInformation(location: '/');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
if (configuration.isDetailsPage) {
return RouteInformation(location: '/book/${configuration.id}');
}
return null;
}
@ -80,9 +82,10 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
final GlobalKey<NavigatorState> navigatorKey;
Book _selectedBook;
Book? _selectedBook;
bool show404 = false;
final List<Book> books = [
@ -93,13 +96,14 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
@override
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
? BookRoutePath.home()
: BookRoutePath.details(books.indexOf(_selectedBook));
: BookRoutePath.details(books.indexOf(_selectedBook!));
}
@override
@ -107,19 +111,22 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
MaterialPage<void>(
key: const ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
const MaterialPage<void>(
key: ValueKey('UnknownPage'),
child: UnknownScreen(),
)
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
BookDetailsPage(book: _selectedBook!)
],
onPopPage: (route, result) {
onPopPage: (route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
@ -135,20 +142,20 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
}
@override
Future<void> setNewRoutePath(BookRoutePath path) {
if (path.isUnknown) {
Future<void> setNewRoutePath(BookRoutePath configuration) {
if (configuration.isUnknown) {
_selectedBook = null;
show404 = true;
return SynchronousFuture<void>(null);
}
if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
if (configuration.isDetailsPage) {
if (configuration.id! < 0 || configuration.id! > books.length - 1) {
show404 = true;
return SynchronousFuture<void>(null);
}
_selectedBook = books[path.id];
_selectedBook = books[configuration.id!];
} else {
_selectedBook = null;
}
@ -163,17 +170,18 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
}
}
class BookDetailsPage extends Page {
class BookDetailsPage extends Page<void> {
final Book book;
BookDetailsPage({
this.book,
required this.book,
}) : super(key: ValueKey(book));
@override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
return MaterialPageRoute<void>(
settings: this,
builder: (BuildContext context) {
builder: (context) {
return BookDetailsScreen(book: book);
},
);
@ -181,7 +189,7 @@ class BookDetailsPage extends Page {
}
class BookRoutePath {
final int id;
final int? id;
final bool isUnknown;
BookRoutePath.home()
@ -203,10 +211,11 @@ class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
const BooksListScreen({
required this.books,
required this.onTapped,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -229,9 +238,10 @@ class BooksListScreen extends StatelessWidget {
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
const BookDetailsScreen({
required this.book,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -242,10 +252,8 @@ class BookDetailsScreen extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
),
),
@ -254,11 +262,13 @@ class BookDetailsScreen extends StatelessWidget {
}
class UnknownScreen extends StatelessWidget {
const UnknownScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
body: const Center(
child: Text('404!'),
),
);

@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(NestedRouterDemo());
runApp(const NestedRouterDemo());
}
class Book {
@ -21,13 +21,15 @@ class Book {
}
class NestedRouterDemo extends StatefulWidget {
const NestedRouterDemo({Key? key}) : super(key: key);
@override
_NestedRouterDemoState createState() => _NestedRouterDemoState();
}
class _NestedRouterDemoState extends State<NestedRouterDemo> {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
final BookRouterDelegate _routerDelegate = BookRouterDelegate();
final BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
@ -43,7 +45,7 @@ class _NestedRouterDemoState extends State<NestedRouterDemo> {
class BooksAppState extends ChangeNotifier {
int _selectedIndex;
Book _selectedBook;
Book? _selectedBook;
final List<Book> books = [
Book('Left Hand of Darkness', 'Ursula K. Le Guin'),
@ -66,16 +68,17 @@ class BooksAppState extends ChangeNotifier {
notifyListeners();
}
Book get selectedBook => _selectedBook;
Book? get selectedBook => _selectedBook;
set selectedBook(Book book) {
set selectedBook(Book? book) {
_selectedBook = book;
notifyListeners();
}
int getSelectedBookById() {
if (_selectedBook == null) return 0;
if (!books.contains(_selectedBook)) return 0;
return books.indexOf(_selectedBook);
return books.indexOf(_selectedBook!);
}
void setSelectedBookById(int id) {
@ -93,14 +96,14 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation,
) {
final uri = Uri.parse(routeInformation.location);
final uri = Uri.parse(routeInformation.location ?? '');
if (uri.pathSegments.isNotEmpty && uri.pathSegments.first == 'settings') {
return SynchronousFuture(BooksSettingsPath());
} else {
if (uri.pathSegments.length >= 2 && uri.pathSegments[0] == 'book') {
return SynchronousFuture(
BooksDetailsPath(int.tryParse(uri.pathSegments[1])),
BooksDetailsPath(int.tryParse(uri.pathSegments[1])!),
);
}
return SynchronousFuture(BooksListPath());
@ -108,12 +111,12 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
}
@override
RouteInformation restoreRouteInformation(BookRoutePath configuration) {
RouteInformation? restoreRouteInformation(BookRoutePath configuration) {
if (configuration is BooksListPath) {
return RouteInformation(location: '/home');
return const RouteInformation(location: '/home');
}
if (configuration is BooksSettingsPath) {
return RouteInformation(location: '/settings');
return const RouteInformation(location: '/settings');
}
if (configuration is BooksDetailsPath) {
return RouteInformation(location: '/book/${configuration.id}');
@ -124,6 +127,7 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
final GlobalKey<NavigatorState> navigatorKey;
BooksAppState appState = BooksAppState();
@ -132,6 +136,7 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
appState.addListener(notifyListeners);
}
@override
BookRoutePath get currentConfiguration {
if (appState.selectedIndex == 1) {
return BooksSettingsPath();
@ -149,11 +154,11 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
MaterialPage<void>(
child: AppShell(appState: appState),
),
],
onPopPage: (route, result) {
onPopPage: (route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
@ -168,14 +173,14 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
}
@override
Future<void> setNewRoutePath(BookRoutePath path) {
if (path is BooksListPath) {
Future<void> setNewRoutePath(BookRoutePath configuration) {
if (configuration is BooksListPath) {
appState.selectedIndex = 0;
appState.selectedBook = null;
} else if (path is BooksSettingsPath) {
} else if (configuration is BooksSettingsPath) {
appState.selectedIndex = 1;
} else if (path is BooksDetailsPath) {
appState.setSelectedBookById(path.id);
} else if (configuration is BooksDetailsPath) {
appState.setSelectedBookById(configuration.id);
}
return SynchronousFuture<void>(null);
}
@ -198,18 +203,20 @@ class BooksDetailsPath extends BookRoutePath {
class AppShell extends StatefulWidget {
final BooksAppState appState;
AppShell({
@required this.appState,
});
const AppShell({
required this.appState,
Key? key,
}) : super(key: key);
@override
_AppShellState createState() => _AppShellState();
}
class _AppShellState extends State<AppShell> {
InnerRouterDelegate _routerDelegate;
ChildBackButtonDispatcher _backButtonDispatcher;
late final InnerRouterDelegate _routerDelegate;
late final ChildBackButtonDispatcher _backButtonDispatcher;
@override
void initState() {
super.initState();
_routerDelegate = InnerRouterDelegate(widget.appState);
@ -226,7 +233,7 @@ class _AppShellState extends State<AppShell> {
super.didChangeDependencies();
// Defer back button dispatching to the child router
_backButtonDispatcher = Router.of(context)
.backButtonDispatcher
.backButtonDispatcher!
.createChildBackButtonDispatcher();
}
@ -245,7 +252,7 @@ class _AppShellState extends State<AppShell> {
backButtonDispatcher: _backButtonDispatcher,
),
bottomNavigationBar: BottomNavigationBar(
items: [
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: 'Settings'),
@ -261,9 +268,12 @@ class _AppShellState extends State<AppShell> {
class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
BooksAppState get appState => _appState;
BooksAppState _appState;
set appState(BooksAppState value) {
if (value == _appState) {
return;
@ -285,20 +295,20 @@ class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
books: appState.books,
onTapped: _handleBookTapped,
),
key: ValueKey('BooksListPage'),
key: const ValueKey('BooksListPage'),
),
if (appState.selectedBook != null)
MaterialPage(
MaterialPage<void>(
key: ValueKey(appState.selectedBook),
child: BookDetailsScreen(book: appState.selectedBook),
child: BookDetailsScreen(book: appState.selectedBook!),
),
] else
FadeAnimationPage(
const FadeAnimationPage(
child: SettingsScreen(),
key: ValueKey('SettingsPage'),
),
],
onPopPage: (route, result) {
onPopPage: (route, dynamic result) {
appState.selectedBook = null;
notifyListeners();
return route.didPop(result);
@ -307,7 +317,7 @@ class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
}
@override
Future<void> setNewRoutePath(BookRoutePath path) {
Future<void> setNewRoutePath(BookRoutePath configuration) {
// This is not required for inner router delegate because it does not
// parse route
assert(false);
@ -320,13 +330,15 @@ class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
}
}
class FadeAnimationPage extends Page {
class FadeAnimationPage extends Page<void> {
final Widget child;
FadeAnimationPage({Key key, this.child}) : super(key: key);
const FadeAnimationPage({LocalKey? key, required this.child})
: super(key: key);
@override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
return PageRouteBuilder<void>(
settings: this,
pageBuilder: (context, animation, animation2) {
var curveTween = CurveTween(curve: Curves.easeIn);
@ -344,10 +356,11 @@ class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
const BooksListScreen({
required this.books,
required this.onTapped,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -369,9 +382,10 @@ class BooksListScreen extends StatelessWidget {
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
const BookDetailsScreen({
required this.book,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -385,9 +399,9 @@ class BookDetailsScreen extends StatelessWidget {
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Back'),
child: const Text('Back'),
),
if (book != null) ...[
...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
@ -399,9 +413,11 @@ class BookDetailsScreen extends StatelessWidget {
}
class SettingsScreen extends StatelessWidget {
const SettingsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
return const Scaffold(
body: Center(
child: Text('Settings screen'),
),

@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
runApp(const BooksApp());
}
class Book {
@ -21,13 +21,15 @@ class Book {
}
class BooksApp extends StatefulWidget {
const BooksApp({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
final BookRouterDelegate _routerDelegate = BookRouterDelegate();
final BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
@ -45,23 +47,23 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation,
) {
final uri = Uri.parse(routeInformation.location);
final uri = Uri.parse(routeInformation.location ?? '');
if (uri.pathSegments.length >= 2) {
var remaining = uri.pathSegments[1];
return SynchronousFuture(BookRoutePath.details(int.tryParse(remaining)));
return SynchronousFuture(BookRoutePath.details(int.tryParse(remaining)!));
} else {
return SynchronousFuture(BookRoutePath.home());
}
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isHomePage) {
return RouteInformation(location: '/');
RouteInformation? restoreRouteInformation(BookRoutePath configuration) {
if (configuration.isHomePage) {
return const RouteInformation(location: '/');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
if (configuration.isDetailsPage) {
return RouteInformation(location: '/book/${configuration.id}');
}
return null;
}
@ -69,9 +71,10 @@ class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
final GlobalKey<NavigatorState> navigatorKey;
Book _selectedBook;
Book? _selectedBook;
final List<Book> books = [
Book('Left Hand of Darkness', 'Ursula K. Le Guin'),
@ -81,9 +84,10 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
@override
BookRoutePath get currentConfiguration => _selectedBook == null
? BookRoutePath.home()
: BookRoutePath.details(books.indexOf(_selectedBook));
: BookRoutePath.details(books.indexOf(_selectedBook!));
@override
Widget build(BuildContext context) {
@ -91,16 +95,16 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
key: navigatorKey,
transitionDelegate: NoAnimationTransitionDelegate(),
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
MaterialPage<void>(
key: const ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (_selectedBook != null) BookDetailsPage(book: _selectedBook)
if (_selectedBook != null) BookDetailsPage(book: _selectedBook!)
],
onPopPage: (route, result) {
onPopPage: (route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
@ -115,9 +119,9 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
}
@override
Future<void> setNewRoutePath(BookRoutePath path) {
if (path.isDetailsPage) {
_selectedBook = books[path.id];
Future<void> setNewRoutePath(BookRoutePath configuration) {
if (configuration.isDetailsPage) {
_selectedBook = books[configuration.id!];
}
return SynchronousFuture<void>(null);
}
@ -128,17 +132,18 @@ class BookRouterDelegate extends RouterDelegate<BookRoutePath>
}
}
class BookDetailsPage extends Page {
class BookDetailsPage extends Page<void> {
final Book book;
BookDetailsPage({
this.book,
required this.book,
}) : super(key: ValueKey(book));
@override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
return MaterialPageRoute<void>(
settings: this,
builder: (BuildContext context) {
builder: (context) {
return BookDetailsScreen(book: book);
},
);
@ -146,7 +151,7 @@ class BookDetailsPage extends Page {
}
class BookRoutePath {
final int id;
final int? id;
BookRoutePath.home() : id = null;
@ -161,10 +166,11 @@ class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
const BooksListScreen({
required this.books,
required this.onTapped,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -187,9 +193,10 @@ class BooksListScreen extends StatelessWidget {
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
const BookDetailsScreen({
required this.book,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -200,10 +207,8 @@ class BookDetailsScreen extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
),
),
@ -214,10 +219,10 @@ class BookDetailsScreen extends StatelessWidget {
class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
@override
Iterable<RouteTransitionRecord> resolve({
List<RouteTransitionRecord> newPageRouteHistory,
Map<RouteTransitionRecord, RouteTransitionRecord>
required List<RouteTransitionRecord> newPageRouteHistory,
required Map<RouteTransitionRecord?, RouteTransitionRecord>
locationToExitingPageRoute,
Map<RouteTransitionRecord, List<RouteTransitionRecord>>
required Map<RouteTransitionRecord?, List<RouteTransitionRecord>>
pageRouteToPagelessRoutes,
}) {
final results = <RouteTransitionRecord>[];

@ -1,6 +1,41 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "22.0.0"
adaptive_breakpoints:
dependency: transitive
description:
name: adaptive_breakpoints
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
adaptive_navigation:
dependency: "direct main"
description:
name: adaptive_navigation
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.3"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.1"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
async:
dependency: transitive
description:
@ -29,6 +64,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
clock:
dependency: transitive
description:
@ -37,12 +79,33 @@ packages:
source: hosted
version: "1.1.0"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
coverage:
dependency: transitive
description:
name: coverage
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
cupertino_icons:
dependency: "direct main"
description:
@ -57,16 +120,84 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
matcher:
dependency: transitive
description:
@ -81,6 +212,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
path:
dependency: transitive
description:
@ -88,11 +240,95 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_to_regexp:
dependency: "direct main"
description:
name: path_to_regexp
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
quiver:
dependency: "direct main"
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.4"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
shelf_static:
dependency: transitive
description:
name: shelf_static
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
source_maps:
dependency: transitive
description:
name: source_maps
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.10"
source_span:
dependency: transitive
description:
@ -128,6 +364,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test:
dependency: "direct dev"
description:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.8"
test_api:
dependency: transitive
description:
@ -135,6 +378,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.19"
typed_data:
dependency: transitive
description:
@ -142,6 +392,55 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.6"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
url_strategy:
dependency: "direct main"
description:
name: url_strategy
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
vector_math:
dependency: transitive
description:
@ -149,5 +448,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "6.2.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=2.0.0"

@ -1,20 +1,23 @@
name: navigation_and_routing
description: Navigation and routing samples
publish_to: 'none'
name: bookstore
description: Navigation and routing sample app
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
cupertino_icons: ^1.0.2
quiver: ^3.0.0
path_to_regexp: ^0.4.0
adaptive_navigation: ^0.0.3
collection: ^1.15.0
url_launcher: ^6.0.0
url_strategy: ^0.2.0
dev_dependencies:
test: ^1.16.0
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
flutter:
uses-material-design: true

@ -0,0 +1,22 @@
// 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:bookstore/src/data/library.dart';
import 'package:test/test.dart';
void main() {
group('Library', () {
test('addBook', () {
final library = Library();
library.addBook('Left Hand of Darkness', 'Ursula K. Le Guin', true, true);
library.addBook('Too Like the Lightning', 'Ada Palmer', false, true);
library.addBook('Kindred', 'Octavia E. Butler', true, false);
library.addBook('The Lathe of Heaven', 'Ursula K. Le Guin', false, false);
expect(library.allAuthors.length, 3);
expect(library.allAuthors.first.books.length, 2);
expect(library.allBooks.length, 4);
expect(library.allBooks.first.author.name, startsWith('Ursula'));
});
});
}

@ -0,0 +1,9 @@
// 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_test/flutter_test.dart';
void main() {
testWidgets('empty test', (tester) async {});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Navigation and routing sample app">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="bookstore">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<title>bookstore</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing ?? reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

@ -0,0 +1,23 @@
{
"name": "bookstore",
"short_name": "bookstore",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "Navigation and routing sample app",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

@ -0,0 +1,3 @@
// Copyright {{ year }} The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file
Loading…
Cancel
Save