├── example
├── ios
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ └── project.pbxproj
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── .gitignore
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ └── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── .metadata
├── README.md
├── pubspec.yaml
├── .gitignore
├── lib
│ ├── screens
│ │ ├── home_screen.dart
│ │ └── second_screen.dart
│ └── main.dart
└── pubspec.lock
├── lib
├── cupertino_will_pop_scope.dart
└── src
│ ├── cupertino_will_pop_scope_page_transitions_builder.dart
│ ├── conditional_will_pop_scope.dart
│ └── cupertino_page_route.dart
├── .metadata
├── pubspec.yaml
├── LICENSE
├── CHANGELOG.md
├── .gitignore
├── README.md
└── pubspec.lock
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/cupertino_will_pop_scope.dart:
--------------------------------------------------------------------------------
1 | library cupertino_will_pop_scope;
2 |
3 | export 'src/conditional_will_pop_scope.dart';
4 | export 'src/cupertino_will_pop_scope_page_transitions_builder.dart';
5 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benPesso/flutter_cupertino_will_pop_scope/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 8874f21e79d7ec66d0457c7ab338348e31b17f1d
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 8874f21e79d7ec66d0457c7ab338348e31b17f1d
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: cupertino_will_pop_scope
2 | description: Enables 'onWillPop' callbacks on Cupertino page transitions and improves visual feedback of rejected "Swipe to go back" gestures.
3 | version: 1.2.1
4 | repository: https://github.com/benPesso/flutter_cupertino_will_pop_scope
5 |
6 | environment:
7 | sdk: '>=2.12.0 <3.0.0'
8 | flutter: ">=1.17.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_test:
16 | sdk: flutter
17 |
18 | flutter:
19 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # CupertinoWillPopScope package example
2 |
3 | A new Flutter package project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Dart
8 | [package](https://flutter.dev/developing-packages/),
9 | a library module containing code that can be shared easily across
10 | multiple Flutter or Dart projects.
11 |
12 | For help getting started with Flutter, view our
13 | [online documentation](https://flutter.dev/docs), which offers tutorials,
14 | samples, guidance on mobile development, and a full API reference.
15 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/cupertino_will_pop_scope_page_transitions_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'cupertino_page_route.dart';
4 |
5 | class CupertinoWillPopScopePageTransionsBuilder extends PageTransitionsBuilder {
6 | const CupertinoWillPopScopePageTransionsBuilder();
7 |
8 | @override
9 | Widget buildTransitions(
10 | PageRoute route,
11 | BuildContext context,
12 | Animation animation,
13 | Animation secondaryAnimation,
14 | Widget child,
15 | ) {
16 | return CupertinoRouteTransitionMixin.buildPageTransitions(
17 | route, context, animation, secondaryAnimation, child);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: Shows how to use the CupertinoWillPopScopePageTransionsBuilder package.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | version: 1.0.0+1
9 |
10 | environment:
11 | sdk: ">=2.7.0 <3.0.0"
12 |
13 | dependencies:
14 | flutter:
15 | sdk: flutter
16 |
17 | cupertino_will_pop_scope:
18 | path: ../
19 |
20 | dev_dependencies:
21 | flutter_test:
22 | sdk: flutter
23 |
24 | flutter:
25 | uses-material-design: true
26 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
--------------------------------------------------------------------------------
/example/lib/screens/home_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'second_screen.dart';
4 |
5 | class HomeScreen extends StatelessWidget {
6 | void _goToSecondScreen(BuildContext context) => Navigator.of(context)
7 | .push(MaterialPageRoute(builder: (_) => SecondScreen()));
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(title: Text('CupertinoWillPopScope Demo')),
13 | body: Center(
14 | child: ElevatedButton(
15 | child: Padding(
16 | padding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
17 | child: Text('Go to second screen'),
18 | ),
19 | onPressed: () => _goToSecondScreen(context),
20 | ),
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:cupertino_will_pop_scope/cupertino_will_pop_scope.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import 'screens/home_screen.dart';
5 |
6 | void main() {
7 | runApp(MyApp());
8 | }
9 |
10 | class MyApp extends StatelessWidget {
11 | final theme = ThemeData(
12 | primarySwatch: Colors.blue,
13 | visualDensity: VisualDensity.adaptivePlatformDensity,
14 | pageTransitionsTheme: PageTransitionsTheme(
15 | builders: {
16 | TargetPlatform.android: ZoomPageTransitionsBuilder(),
17 | TargetPlatform.iOS: CupertinoWillPopScopePageTransionsBuilder(),
18 | },
19 | ),
20 | );
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return MaterialApp(
25 | theme: theme,
26 | home: HomeScreen(),
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Ben Pesso
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 | The above copyright notice and this permission notice shall be included in all
10 | copies or substantial portions of the Software.
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17 | SOFTWARE.
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [1.2.1]
2 | - Merged with latest version of Flutter's `cupertino_page_route.dart`.
3 | ## [1.2.0]
4 | - [BREAKING] Renamed `ConditionalWillPopScope`'s `shouldAddCallbacks` to `shouldAddCallback`.
5 | - Changed all properties of `ConditionalWillPopScope` to be required.
6 | - Changed the logic for dismissing the swipe back gesture on iOS to use the "drag cancelled" handler, instead of the "darg ended" handler.
7 |
8 | ## [1.1.0]
9 |
10 | - Updated with latest version of Flutter's `route.dart` file.
11 | - Update sample app to use `ElevatedButton` instead of the deprecated `RaisedButton` widget.
12 | - Changed `ConditionalWillPopScope.onWillPop` to be optional.
13 | - Migrated to Null Safety.
14 |
15 | ## [1.0.4]
16 |
17 | - Fixed documentation.
18 |
19 | ## [1.0.3]
20 |
21 | - Fixed formatting.
22 |
23 | ## [1.0.2]
24 |
25 | - Fixed Flutter's own asserts, which fail Pub.dev's static analysis.
26 | - Updated the package description.
27 |
28 | ## [1.0.1]
29 |
30 | - Introduced a new `ConditionalWillPopScope` widget which makes it possible to add `willPop` callbacks conditionally.
31 | - Added a small time delay before calling the route's `willPop` callbacks, to accomodate for the screen's "rewind" animation.
32 | - Increased the distance a screen is allowed to be dragged before the gesture is terminated. (From 33% to 42%.)
33 | - Updated the example app and docs.
34 |
35 | ## [1.0.0]
36 |
37 | - Initial release.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Flutter.podspec
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/app.flx
64 | **/ios/Flutter/app.zip
65 | **/ios/Flutter/flutter_assets/
66 | **/ios/Flutter/flutter_export_environment.sh
67 | **/ios/ServiceDefinitions.json
68 | **/ios/Runner/GeneratedPluginRegistrant.*
69 |
70 | # Exceptions to above rules.
71 | !**/ios/**/default.mode1v3
72 | !**/ios/**/default.mode2v3
73 | !**/ios/**/default.pbxuser
74 | !**/ios/**/default.perspectivev3
75 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 29
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.example.example"
42 | minSdkVersion 16
43 | targetSdkVersion 29
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/example/lib/screens/second_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:cupertino_will_pop_scope/cupertino_will_pop_scope.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class SecondScreen extends StatefulWidget {
5 | @override
6 | _SecondScreenState createState() => _SecondScreenState();
7 | }
8 |
9 | class _SecondScreenState extends State {
10 | final Color _color = Colors.pink;
11 |
12 | /// Holds the state of the screen.
13 | bool _hasChanges = true;
14 |
15 | /// Shows an alert and returns `false` when `_hasChanges` is `true`.
16 | /// This prevents the navigator from popping this route.
17 | Future _onWillPop() async {
18 | if (_hasChanges) {
19 | // Show an alert before returning `false`.
20 | showDialog(
21 | context: context,
22 | builder: (context) => AlertDialog(
23 | content: Text('Back navigation is disabled.'),
24 | ),
25 | );
26 |
27 | // Return `false` to prevent the route from popping.
28 | return false;
29 | }
30 |
31 | return true;
32 | }
33 |
34 | /// Updates `_hasChanges` with the provided value.
35 | void _updateChanges(bool value) => setState(() => _hasChanges = value);
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | return ConditionalWillPopScope(
40 | onWillPop: _onWillPop,
41 | shouldAddCallback: _hasChanges,
42 | child: Scaffold(
43 | appBar: AppBar(title: Text('Second Screen'), backgroundColor: _color),
44 | body: Center(
45 | child: Container(
46 | child: SwitchListTile(
47 | activeColor: _color,
48 | onChanged: _updateChanges,
49 | title: Text('Disable back navigation'),
50 | value: _hasChanges,
51 | ),
52 | decoration: BoxDecoration(
53 | border: Border.all(color: _color),
54 | borderRadius: BorderRadius.circular(6.0),
55 | ),
56 | margin: EdgeInsets.symmetric(horizontal: 12.0),
57 | ),
58 | ),
59 | ),
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
23 |
27 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/lib/src/conditional_will_pop_scope.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// An enhanced version of [WillPopScope] that adds additional functionality.
4 | class ConditionalWillPopScope extends StatefulWidget {
5 | /// Creates a widget that registers a callback to veto attempts by the user to
6 | /// dismiss the enclosing [ModalRoute].
7 | ///
8 | /// The `child` argument must not be `null`.
9 | const ConditionalWillPopScope({
10 | Key? key,
11 | required this.child,
12 | required this.onWillPop,
13 | required this.shouldAddCallback,
14 | }) : super(key: key);
15 |
16 | /// The widget below this widget in the tree.
17 | final Widget child;
18 |
19 | /// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
20 | ///
21 | /// If the callback returns a [Future] that resolves to `false`, the enclosing
22 | /// route will not be popped.
23 | final WillPopCallback? onWillPop;
24 |
25 | /// Determines if the `onWillPop` callback should be added to the enclosing [ModalRoute].
26 | final bool shouldAddCallback;
27 |
28 | @override
29 | _ConditionalWillPopScopeState createState() =>
30 | _ConditionalWillPopScopeState();
31 | }
32 |
33 | class _ConditionalWillPopScopeState extends State {
34 | ModalRoute? _route;
35 |
36 | @override
37 | void didChangeDependencies() {
38 | super.didChangeDependencies();
39 |
40 | if (widget.onWillPop != null) {
41 | // Remove callback from the "old" route.
42 | _route?.removeScopedWillPopCallback(widget.onWillPop!);
43 |
44 | // Update the reference to the "current" route.
45 | _route = ModalRoute.of(context);
46 |
47 | // Add the callbacks to the new "current" route.
48 | if (widget.shouldAddCallback)
49 | _route?.addScopedWillPopCallback(widget.onWillPop!);
50 | }
51 | }
52 |
53 | @override
54 | void didUpdateWidget(ConditionalWillPopScope oldWidget) {
55 | super.didUpdateWidget(oldWidget);
56 |
57 | assert(_route == ModalRoute.of(context));
58 |
59 | if (widget.onWillPop != oldWidget.onWillPop ||
60 | widget.shouldAddCallback != oldWidget.shouldAddCallback) {
61 | // Remove callbacks of the old widget state.
62 | if (oldWidget.onWillPop != null)
63 | _route?.removeScopedWillPopCallback(oldWidget.onWillPop!);
64 |
65 | // Add callbacks of the new widget state.
66 | if (widget.onWillPop != null && widget.shouldAddCallback)
67 | _route?.addScopedWillPopCallback(widget.onWillPop!);
68 | }
69 | }
70 |
71 | @override
72 | void dispose() {
73 | if (widget.onWillPop != null)
74 | _route?.removeScopedWillPopCallback(widget.onWillPop!);
75 | super.dispose();
76 | }
77 |
78 | @override
79 | Widget build(BuildContext context) => widget.child;
80 | }
81 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CupertinoWillPopScope
2 |
3 | [](https://pub.dev/packages/cupertino_will_pop_scope)
4 |
5 | > _Note: This package can be used on any platform, and is not specific to iOS. `ConditionalWillPopScope` and `CupertinoWillPopScopePageTransionsBuilder` can each be used seprately._
6 |
7 |
8 |
9 | ### CupertinoWillPopScopePageTransionsBuilder
10 |
11 | The key to this package is a modified version of Flutter's [CupertinoPageRoute](https://api.flutter.dev/flutter/cupertino/CupertinoPageRoute-class.html), which has been enhanced with the following:
12 | - Visual feedback when users attempt to "swipe to go back" - the screen is allowed to be dragged a bit before it is snapped back to place.
13 | - If an enclosing route has `willPop` callbacks, they are triggered once the screen is snapped back to place.
14 |
15 |
16 | ### ConditionalWillPopScope
17 |
18 | When using this widget, be sure to update `shouldAddCallbacks` based on the state of your screen, and only set it to `true` when the screen **should not be allowed to pop**. Additionally, the `onWillPop` property should not be changed once set.
19 |
20 | -------
21 |
22 | A working app using this package can be found in the [example](example/lib/main.dart) folder.
23 |
24 | ## Usage
25 |
26 | To use this package, add `cupertino_will_pop_scope` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/).
27 |
28 |
29 | ## Example
30 | ### Import the library
31 |
32 | ``` dart
33 | // main.dart
34 | import 'package:decorated_icon/cupertino_will_pop_scope.dart';
35 | ```
36 |
37 | ### Configure page transitions
38 | Set the transition builder of the desired platform to `CupertinoWillPopScopePageTransionsBuilder` in your theme configuration.
39 | ```dart
40 | // main.dart
41 | theme = ThemeData(
42 | ...
43 | pageTransitionsTheme: PageTransitionsTheme(
44 | builders: {
45 | TargetPlatform.android: ZoomPageTransitionsBuilder(),
46 | TargetPlatform.iOS: CupertinoWillPopScopePageTransionsBuilder(),
47 | },
48 | ),
49 | );
50 | ```
51 |
52 | > ###### Make sure the theme is applied to the app.
53 | ```dart
54 | // main.dart
55 | MaterialApp(
56 | ...
57 | theme: theme,
58 | home: HomeScreen(),
59 | );
60 | ```
61 |
62 | ### Wrap your screen
63 | Using the included `ConditionalWillPopScope` widget, or Flutter's [WillPopScope](https://api.flutter.dev/flutter/widgets/WillPopScope-class.html) widget, wrap your screen and define an `onWillPop` callback for it.
64 | > ##### Note that `onWillPop` should always return a `bool`. See the [Flutter Docs](https://api.flutter.dev/flutter/widgets/WillPopScope-class.html) for more.
65 |
66 | ###### Using ConditionalWillPopScope:
67 | ```dart
68 | // my_screen.dart
69 | @override
70 | Widget build(BuildContext context) {
71 | return ConditionalWillPopScope(
72 | child: _MyScreenContent(),
73 | onWillPop: _onWillPop,
74 | shouldAddCallbacks: _hasChanges,
75 | );
76 | }
77 | ```
78 |
79 | ###### Using WillPopScope:
80 | ```dart
81 | // my_screen.dart
82 | @override
83 | Widget build(BuildContext context) {
84 | return WillPopScope(
85 | child: _MyScreenContent(),
86 | onWillPop: _hasChanges ? _onWillPop : null,
87 | );
88 | }
89 | ```
90 |
91 | > :warning: When using Flutter's `WillPopScope` widget, the `onWillPop` must be set conditionally. Otherwise, the "swipe to go back" gesture will be cancelled. In the example above, this is achieved by using `_hasChanges` as the condition.
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.8.2"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.2.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.3.1"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.16.0"
46 | fake_async:
47 | dependency: transitive
48 | description:
49 | name: fake_async
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.3.0"
53 | flutter:
54 | dependency: "direct main"
55 | description: flutter
56 | source: sdk
57 | version: "0.0.0"
58 | flutter_test:
59 | dependency: "direct dev"
60 | description: flutter
61 | source: sdk
62 | version: "0.0.0"
63 | matcher:
64 | dependency: transitive
65 | description:
66 | name: matcher
67 | url: "https://pub.dartlang.org"
68 | source: hosted
69 | version: "0.12.11"
70 | material_color_utilities:
71 | dependency: transitive
72 | description:
73 | name: material_color_utilities
74 | url: "https://pub.dartlang.org"
75 | source: hosted
76 | version: "0.1.4"
77 | meta:
78 | dependency: transitive
79 | description:
80 | name: meta
81 | url: "https://pub.dartlang.org"
82 | source: hosted
83 | version: "1.7.0"
84 | path:
85 | dependency: transitive
86 | description:
87 | name: path
88 | url: "https://pub.dartlang.org"
89 | source: hosted
90 | version: "1.8.1"
91 | sky_engine:
92 | dependency: transitive
93 | description: flutter
94 | source: sdk
95 | version: "0.0.99"
96 | source_span:
97 | dependency: transitive
98 | description:
99 | name: source_span
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "1.8.2"
103 | stack_trace:
104 | dependency: transitive
105 | description:
106 | name: stack_trace
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "1.10.0"
110 | stream_channel:
111 | dependency: transitive
112 | description:
113 | name: stream_channel
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "2.1.0"
117 | string_scanner:
118 | dependency: transitive
119 | description:
120 | name: string_scanner
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "1.1.0"
124 | term_glyph:
125 | dependency: transitive
126 | description:
127 | name: term_glyph
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "1.2.0"
131 | test_api:
132 | dependency: transitive
133 | description:
134 | name: test_api
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "0.4.9"
138 | vector_math:
139 | dependency: transitive
140 | description:
141 | name: vector_math
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "2.1.2"
145 | sdks:
146 | dart: ">=2.17.0-0 <3.0.0"
147 | flutter: ">=1.17.0"
148 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.8.2"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.2.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.3.1"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.16.0"
46 | cupertino_will_pop_scope:
47 | dependency: "direct main"
48 | description:
49 | path: ".."
50 | relative: true
51 | source: path
52 | version: "1.2.1"
53 | fake_async:
54 | dependency: transitive
55 | description:
56 | name: fake_async
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "1.3.0"
60 | flutter:
61 | dependency: "direct main"
62 | description: flutter
63 | source: sdk
64 | version: "0.0.0"
65 | flutter_test:
66 | dependency: "direct dev"
67 | description: flutter
68 | source: sdk
69 | version: "0.0.0"
70 | matcher:
71 | dependency: transitive
72 | description:
73 | name: matcher
74 | url: "https://pub.dartlang.org"
75 | source: hosted
76 | version: "0.12.11"
77 | material_color_utilities:
78 | dependency: transitive
79 | description:
80 | name: material_color_utilities
81 | url: "https://pub.dartlang.org"
82 | source: hosted
83 | version: "0.1.4"
84 | meta:
85 | dependency: transitive
86 | description:
87 | name: meta
88 | url: "https://pub.dartlang.org"
89 | source: hosted
90 | version: "1.7.0"
91 | path:
92 | dependency: transitive
93 | description:
94 | name: path
95 | url: "https://pub.dartlang.org"
96 | source: hosted
97 | version: "1.8.1"
98 | sky_engine:
99 | dependency: transitive
100 | description: flutter
101 | source: sdk
102 | version: "0.0.99"
103 | source_span:
104 | dependency: transitive
105 | description:
106 | name: source_span
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "1.8.2"
110 | stack_trace:
111 | dependency: transitive
112 | description:
113 | name: stack_trace
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "1.10.0"
117 | stream_channel:
118 | dependency: transitive
119 | description:
120 | name: stream_channel
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "2.1.0"
124 | string_scanner:
125 | dependency: transitive
126 | description:
127 | name: string_scanner
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "1.1.0"
131 | term_glyph:
132 | dependency: transitive
133 | description:
134 | name: term_glyph
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "1.2.0"
138 | test_api:
139 | dependency: transitive
140 | description:
141 | name: test_api
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "0.4.9"
145 | vector_math:
146 | dependency: transitive
147 | description:
148 | name: vector_math
149 | url: "https://pub.dartlang.org"
150 | source: hosted
151 | version: "2.1.2"
152 | sdks:
153 | dart: ">=2.17.0-0 <3.0.0"
154 | flutter: ">=1.17.0"
155 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | buildActionMask = 2147483647;
175 | files = (
176 | );
177 | inputPaths = (
178 | );
179 | name = "Thin Binary";
180 | outputPaths = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
185 | };
186 | 9740EEB61CF901F6004384FC /* Run Script */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | );
193 | name = "Run Script";
194 | outputPaths = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | shellPath = /bin/sh;
198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
199 | };
200 | /* End PBXShellScriptBuildPhase section */
201 |
202 | /* Begin PBXSourcesBuildPhase section */
203 | 97C146EA1CF9000F007C117D /* Sources */ = {
204 | isa = PBXSourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
209 | );
210 | runOnlyForDeploymentPostprocessing = 0;
211 | };
212 | /* End PBXSourcesBuildPhase section */
213 |
214 | /* Begin PBXVariantGroup section */
215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
216 | isa = PBXVariantGroup;
217 | children = (
218 | 97C146FB1CF9000F007C117D /* Base */,
219 | );
220 | name = Main.storyboard;
221 | sourceTree = "";
222 | };
223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C147001CF9000F007C117D /* Base */,
227 | );
228 | name = LaunchScreen.storyboard;
229 | sourceTree = "";
230 | };
231 | /* End PBXVariantGroup section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
240 | CLANG_CXX_LIBRARY = "libc++";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_EMPTY_BODY = YES;
250 | CLANG_WARN_ENUM_CONVERSION = YES;
251 | CLANG_WARN_INFINITE_RECURSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu99;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | SDKROOT = iphoneos;
278 | SUPPORTED_PLATFORMS = iphoneos;
279 | TARGETED_DEVICE_FAMILY = "1,2";
280 | VALIDATE_PRODUCT = YES;
281 | };
282 | name = Profile;
283 | };
284 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
285 | isa = XCBuildConfiguration;
286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CLANG_ENABLE_MODULES = YES;
290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
291 | ENABLE_BITCODE = NO;
292 | FRAMEWORK_SEARCH_PATHS = (
293 | "$(inherited)",
294 | "$(PROJECT_DIR)/Flutter",
295 | );
296 | INFOPLIST_FILE = Runner/Info.plist;
297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
298 | LIBRARY_SEARCH_PATHS = (
299 | "$(inherited)",
300 | "$(PROJECT_DIR)/Flutter",
301 | );
302 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
303 | PRODUCT_NAME = "$(TARGET_NAME)";
304 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
305 | SWIFT_VERSION = 5.0;
306 | VERSIONING_SYSTEM = "apple-generic";
307 | };
308 | name = Profile;
309 | };
310 | 97C147031CF9000F007C117D /* Debug */ = {
311 | isa = XCBuildConfiguration;
312 | buildSettings = {
313 | ALWAYS_SEARCH_USER_PATHS = NO;
314 | CLANG_ANALYZER_NONNULL = YES;
315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
316 | CLANG_CXX_LIBRARY = "libc++";
317 | CLANG_ENABLE_MODULES = YES;
318 | CLANG_ENABLE_OBJC_ARC = YES;
319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
320 | CLANG_WARN_BOOL_CONVERSION = YES;
321 | CLANG_WARN_COMMA = YES;
322 | CLANG_WARN_CONSTANT_CONVERSION = YES;
323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
325 | CLANG_WARN_EMPTY_BODY = YES;
326 | CLANG_WARN_ENUM_CONVERSION = YES;
327 | CLANG_WARN_INFINITE_RECURSION = YES;
328 | CLANG_WARN_INT_CONVERSION = YES;
329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
334 | CLANG_WARN_STRICT_PROTOTYPES = YES;
335 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
336 | CLANG_WARN_UNREACHABLE_CODE = YES;
337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
339 | COPY_PHASE_STRIP = NO;
340 | DEBUG_INFORMATION_FORMAT = dwarf;
341 | ENABLE_STRICT_OBJC_MSGSEND = YES;
342 | ENABLE_TESTABILITY = YES;
343 | GCC_C_LANGUAGE_STANDARD = gnu99;
344 | GCC_DYNAMIC_NO_PIC = NO;
345 | GCC_NO_COMMON_BLOCKS = YES;
346 | GCC_OPTIMIZATION_LEVEL = 0;
347 | GCC_PREPROCESSOR_DEFINITIONS = (
348 | "DEBUG=1",
349 | "$(inherited)",
350 | );
351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
353 | GCC_WARN_UNDECLARED_SELECTOR = YES;
354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
355 | GCC_WARN_UNUSED_FUNCTION = YES;
356 | GCC_WARN_UNUSED_VARIABLE = YES;
357 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
358 | MTL_ENABLE_DEBUG_INFO = YES;
359 | ONLY_ACTIVE_ARCH = YES;
360 | SDKROOT = iphoneos;
361 | TARGETED_DEVICE_FAMILY = "1,2";
362 | };
363 | name = Debug;
364 | };
365 | 97C147041CF9000F007C117D /* Release */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ALWAYS_SEARCH_USER_PATHS = NO;
369 | CLANG_ANALYZER_NONNULL = YES;
370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
371 | CLANG_CXX_LIBRARY = "libc++";
372 | CLANG_ENABLE_MODULES = YES;
373 | CLANG_ENABLE_OBJC_ARC = YES;
374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
375 | CLANG_WARN_BOOL_CONVERSION = YES;
376 | CLANG_WARN_COMMA = YES;
377 | CLANG_WARN_CONSTANT_CONVERSION = YES;
378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
380 | CLANG_WARN_EMPTY_BODY = YES;
381 | CLANG_WARN_ENUM_CONVERSION = YES;
382 | CLANG_WARN_INFINITE_RECURSION = YES;
383 | CLANG_WARN_INT_CONVERSION = YES;
384 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
385 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
386 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
389 | CLANG_WARN_STRICT_PROTOTYPES = YES;
390 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
391 | CLANG_WARN_UNREACHABLE_CODE = YES;
392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
394 | COPY_PHASE_STRIP = NO;
395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
396 | ENABLE_NS_ASSERTIONS = NO;
397 | ENABLE_STRICT_OBJC_MSGSEND = YES;
398 | GCC_C_LANGUAGE_STANDARD = gnu99;
399 | GCC_NO_COMMON_BLOCKS = YES;
400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
402 | GCC_WARN_UNDECLARED_SELECTOR = YES;
403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
404 | GCC_WARN_UNUSED_FUNCTION = YES;
405 | GCC_WARN_UNUSED_VARIABLE = YES;
406 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
407 | MTL_ENABLE_DEBUG_INFO = NO;
408 | SDKROOT = iphoneos;
409 | SUPPORTED_PLATFORMS = iphoneos;
410 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
411 | TARGETED_DEVICE_FAMILY = "1,2";
412 | VALIDATE_PRODUCT = YES;
413 | };
414 | name = Release;
415 | };
416 | 97C147061CF9000F007C117D /* Debug */ = {
417 | isa = XCBuildConfiguration;
418 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
419 | buildSettings = {
420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
421 | CLANG_ENABLE_MODULES = YES;
422 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
423 | ENABLE_BITCODE = NO;
424 | FRAMEWORK_SEARCH_PATHS = (
425 | "$(inherited)",
426 | "$(PROJECT_DIR)/Flutter",
427 | );
428 | INFOPLIST_FILE = Runner/Info.plist;
429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
430 | LIBRARY_SEARCH_PATHS = (
431 | "$(inherited)",
432 | "$(PROJECT_DIR)/Flutter",
433 | );
434 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
435 | PRODUCT_NAME = "$(TARGET_NAME)";
436 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
437 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
438 | SWIFT_VERSION = 5.0;
439 | VERSIONING_SYSTEM = "apple-generic";
440 | };
441 | name = Debug;
442 | };
443 | 97C147071CF9000F007C117D /* Release */ = {
444 | isa = XCBuildConfiguration;
445 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
446 | buildSettings = {
447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
448 | CLANG_ENABLE_MODULES = YES;
449 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
450 | ENABLE_BITCODE = NO;
451 | FRAMEWORK_SEARCH_PATHS = (
452 | "$(inherited)",
453 | "$(PROJECT_DIR)/Flutter",
454 | );
455 | INFOPLIST_FILE = Runner/Info.plist;
456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
457 | LIBRARY_SEARCH_PATHS = (
458 | "$(inherited)",
459 | "$(PROJECT_DIR)/Flutter",
460 | );
461 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
462 | PRODUCT_NAME = "$(TARGET_NAME)";
463 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
464 | SWIFT_VERSION = 5.0;
465 | VERSIONING_SYSTEM = "apple-generic";
466 | };
467 | name = Release;
468 | };
469 | /* End XCBuildConfiguration section */
470 |
471 | /* Begin XCConfigurationList section */
472 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
473 | isa = XCConfigurationList;
474 | buildConfigurations = (
475 | 97C147031CF9000F007C117D /* Debug */,
476 | 97C147041CF9000F007C117D /* Release */,
477 | 249021D3217E4FDB00AE95B9 /* Profile */,
478 | );
479 | defaultConfigurationIsVisible = 0;
480 | defaultConfigurationName = Release;
481 | };
482 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
483 | isa = XCConfigurationList;
484 | buildConfigurations = (
485 | 97C147061CF9000F007C117D /* Debug */,
486 | 97C147071CF9000F007C117D /* Release */,
487 | 249021D4217E4FDB00AE95B9 /* Profile */,
488 | );
489 | defaultConfigurationIsVisible = 0;
490 | defaultConfigurationName = Release;
491 | };
492 | /* End XCConfigurationList section */
493 | };
494 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
495 | }
496 |
--------------------------------------------------------------------------------
/lib/src/cupertino_page_route.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // This is a modified copy of Flutter's Cupertino `route.dart` file.
6 |
7 | import 'dart:async';
8 | import 'dart:math';
9 | import 'dart:ui' show lerpDouble, ImageFilter;
10 |
11 | import 'package:flutter/cupertino.dart';
12 | import 'package:flutter/foundation.dart';
13 | import 'package:flutter/gestures.dart';
14 | import 'package:flutter/rendering.dart';
15 |
16 | const double _kBackGestureWidth = 20.0;
17 | final double _kMaxSwipeDistance = 0.42; // As portion of screen.
18 | const double _kMinFlingVelocity = 1.0; // Screen widths per second.
19 |
20 | // An eyeballed value for the maximum time it takes for a page to animate forward
21 | // if the user releases a page mid swipe.
22 | const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
23 |
24 | // The maximum time for a page to get reset to it's original position if the
25 | // user releases a page mid swipe.
26 | const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
27 |
28 | /// Barrier color used for a barrier visible during transitions for Cupertino
29 | /// page routes.
30 | ///
31 | /// This barrier color is only used for full-screen page routes with
32 | /// `fullscreenDialog: false`.
33 | ///
34 | /// By default, `fullscreenDialog` Cupertino route transitions have no
35 | /// `barrierColor`, and [CupertinoDialogRoute]s and [CupertinoModalPopupRoute]s
36 | /// have a `barrierColor` defined by [kCupertinoModalBarrierColor].
37 | ///
38 | /// A relatively rigorous eyeball estimation.
39 | const Color _kCupertinoPageTransitionBarrierColor = Color(0x18000000);
40 |
41 | /// Barrier color for a Cupertino modal barrier.
42 | ///
43 | /// Extracted from https://developer.apple.com/design/resources/.
44 | const Color kCupertinoModalBarrierColor = CupertinoDynamicColor.withBrightness(
45 | color: Color(0x33000000),
46 | darkColor: Color(0x7A000000),
47 | );
48 |
49 | // The duration of the transition used when a modal popup is shown.
50 | const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
51 |
52 | // Offset from offscreen to the right to fully on screen.
53 | final Animatable _kRightMiddleTween = Tween(
54 | begin: const Offset(1.0, 0.0),
55 | end: Offset.zero,
56 | );
57 |
58 | // Offset from fully on screen to 1/3 offscreen to the left.
59 | final Animatable _kMiddleLeftTween = Tween(
60 | begin: Offset.zero,
61 | end: const Offset(-1.0 / 3.0, 0.0),
62 | );
63 |
64 | // Offset from offscreen below to fully on screen.
65 | final Animatable _kBottomUpTween = Tween(
66 | begin: const Offset(0.0, 1.0),
67 | end: Offset.zero,
68 | );
69 |
70 | /// A mixin that replaces the entire screen with an iOS transition for a
71 | /// [PageRoute].
72 | ///
73 | /// {@template flutter.cupertino.cupertinoRouteTransitionMixin}
74 | /// The page slides in from the right and exits in reverse. The page also shifts
75 | /// to the left in parallax when another page enters to cover it.
76 | ///
77 | /// The page slides in from the bottom and exits in reverse with no parallax
78 | /// effect for fullscreen dialogs.
79 | /// {@endtemplate}
80 | ///
81 | /// See also:
82 | ///
83 | /// * [MaterialRouteTransitionMixin], which is a mixin that provides
84 | /// platform-appropriate transitions for a [PageRoute].
85 | /// * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin.
86 | mixin CupertinoRouteTransitionMixin on PageRoute {
87 | /// Builds the primary contents of the route.
88 | @protected
89 | Widget buildContent(BuildContext context);
90 |
91 | /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
92 | /// A title string for this route.
93 | ///
94 | /// Used to auto-populate [CupertinoNavigationBar] and
95 | /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
96 | /// one is not manually supplied.
97 | /// {@endtemplate}
98 | String? get title;
99 |
100 | ValueNotifier? _previousTitle;
101 |
102 | /// The title string of the previous [CupertinoPageRoute].
103 | ///
104 | /// The [ValueListenable]'s value is readable after the route is installed
105 | /// onto a [Navigator]. The [ValueListenable] will also notify its listeners
106 | /// if the value changes (such as by replacing the previous route).
107 | ///
108 | /// The [ValueListenable] itself will be null before the route is installed.
109 | /// Its content value will be null if the previous route has no title or
110 | /// is not a [CupertinoPageRoute].
111 | ///
112 | /// See also:
113 | ///
114 | /// * [ValueListenableBuilder], which can be used to listen and rebuild
115 | /// widgets based on a ValueListenable.
116 | ValueListenable get previousTitle {
117 | assert(
118 | _previousTitle != null,
119 | 'Cannot read the previousTitle for a route that has not yet been installed',
120 | );
121 | return _previousTitle!;
122 | }
123 |
124 | @override
125 | void didChangePrevious(Route? previousRoute) {
126 | final String? previousTitleString =
127 | previousRoute is CupertinoRouteTransitionMixin
128 | ? previousRoute.title
129 | : null;
130 | if (_previousTitle == null) {
131 | _previousTitle = ValueNotifier(previousTitleString);
132 | } else {
133 | _previousTitle!.value = previousTitleString;
134 | }
135 | super.didChangePrevious(previousRoute);
136 | }
137 |
138 | @override
139 | // A relatively rigorous eyeball estimation.
140 | Duration get transitionDuration => const Duration(milliseconds: 400);
141 |
142 | @override
143 | Color? get barrierColor =>
144 | fullscreenDialog ? null : _kCupertinoPageTransitionBarrierColor;
145 |
146 | @override
147 | String? get barrierLabel => null;
148 |
149 | @override
150 | bool canTransitionTo(TransitionRoute nextRoute) {
151 | // Don't perform outgoing animation if the next route is a fullscreen dialog.
152 | return nextRoute is CupertinoRouteTransitionMixin &&
153 | !nextRoute.fullscreenDialog;
154 | }
155 |
156 | /// True if an iOS-style back swipe pop gesture is currently underway for [route].
157 | ///
158 | /// This just check the route's [NavigatorState.userGestureInProgress].
159 | ///
160 | /// See also:
161 | ///
162 | /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
163 | /// would be allowed.
164 | static bool isPopGestureInProgress(PageRoute route) {
165 | return route.navigator!.userGestureInProgress;
166 | }
167 |
168 | /// True if an iOS-style back swipe pop gesture is currently underway for this route.
169 | ///
170 | /// See also:
171 | ///
172 | /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
173 | /// is currently underway for specific route.
174 | /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
175 | /// would be allowed.
176 | bool get popGestureInProgress => isPopGestureInProgress(this);
177 |
178 | /// Whether a pop gesture can be started by the user.
179 | ///
180 | /// Returns true if the user can edge-swipe to a previous route.
181 | ///
182 | /// Returns false once [isPopGestureInProgress] is true, but
183 | /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
184 | /// true first.
185 | ///
186 | /// This should only be used between frames, not during build.
187 | bool get popGestureEnabled => _isPopGestureEnabled(this);
188 |
189 | static bool _isPopGestureEnabled(PageRoute route) {
190 | // If there's nothing to go back to, then obviously we don't support
191 | // the back gesture.
192 | if (route.isFirst) return false;
193 | // If the route wouldn't actually pop if we popped it, then the gesture
194 | // would be really confusing (or would skip internal routes), so disallow it.
195 | if (route.willHandlePopInternally) return false;
196 | // If attempts to dismiss this route might be vetoed such as in a page
197 | // with forms, then do not allow the user to dismiss the route with a swipe.
198 |
199 | //// IGNORED SO WE CAN HANDLE A POP ATTEMPT PROPERLY
200 | //// if (route.hasScopedWillPopCallback)
201 | //// return false;
202 |
203 | // Fullscreen dialogs aren't dismissible by back swipe.
204 | if (route.fullscreenDialog) return false;
205 | // If we're in an animation already, we cannot be manually swiped.
206 | if (route.animation!.status != AnimationStatus.completed) return false;
207 | // If we're being popped into, we also cannot be swiped until the pop above
208 | // it completes. This translates to our secondary animation being
209 | // dismissed.
210 | if (route.secondaryAnimation!.status != AnimationStatus.dismissed)
211 | return false;
212 | // If we're in a gesture already, we cannot start another.
213 | if (isPopGestureInProgress(route)) return false;
214 |
215 | // Looks like a back gesture would be welcome!
216 | return true;
217 | }
218 |
219 | @override
220 | Widget buildPage(BuildContext context, Animation animation,
221 | Animation secondaryAnimation) {
222 | final Widget child = buildContent(context);
223 | final Widget result = Semantics(
224 | scopesRoute: true,
225 | explicitChildNodes: true,
226 | child: child,
227 | );
228 | assert(() {
229 | // `child` has a non-nullable return type, but might be null when
230 | // running with weak checking, so we need to null check it anyway (and
231 | // ignore the warning that the null-handling logic is dead code).
232 | if (child == null) {
233 | // ignore: dead_code
234 | throw FlutterError.fromParts([
235 | ErrorSummary(
236 | 'The builder for route "${settings.name}" returned null.'),
237 | ErrorDescription('Route builders must never return null.'),
238 | ]);
239 | }
240 | return true;
241 | }());
242 | return result;
243 | }
244 |
245 | //// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
246 | //// gesture is detected. It reutrns the route's `willPop` callback(s).
247 | static Future Function()? _getRouteWillPopCallbacks(
248 | PageRoute route) =>
249 | (route.hasScopedWillPopCallback) ? route.willPop : null;
250 |
251 | // Called by _CupertinoBackGestureDetector when a pop ("back") drag start
252 | // gesture is detected. The returned controller handles all of the subsequent
253 | // drag events.
254 | static _CupertinoBackGestureController _startPopGesture(
255 | PageRoute route) {
256 | assert(_isPopGestureEnabled(route));
257 |
258 | return _CupertinoBackGestureController(
259 | navigator: route.navigator!,
260 | controller: route.controller!, // protected access
261 | );
262 | }
263 |
264 | /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full
265 | /// screen dialog, otherwise a [CupertinoPageTransition] is returned.
266 | ///
267 | /// Used by [CupertinoPageRoute.buildTransitions].
268 | ///
269 | /// This method can be applied to any [PageRoute], not just
270 | /// [CupertinoPageRoute]. It's typically used to provide a Cupertino style
271 | /// horizontal transition for material widgets when the target platform
272 | /// is [TargetPlatform.iOS].
273 | ///
274 | /// See also:
275 | ///
276 | /// * [CupertinoPageTransitionsBuilder], which uses this method to define a
277 | /// [PageTransitionsBuilder] for the [PageTransitionsTheme].
278 | static Widget buildPageTransitions(
279 | PageRoute route,
280 | BuildContext context,
281 | Animation animation,
282 | Animation secondaryAnimation,
283 | Widget child,
284 | ) {
285 | // Check if the route has an animation that's currently participating
286 | // in a back swipe gesture.
287 | //
288 | // In the middle of a back gesture drag, let the transition be linear to
289 | // match finger motions.
290 | final bool linearTransition = isPopGestureInProgress(route);
291 | if (route.fullscreenDialog) {
292 | return CupertinoFullscreenDialogTransition(
293 | primaryRouteAnimation: animation,
294 | secondaryRouteAnimation: secondaryAnimation,
295 | linearTransition: linearTransition,
296 | child: child,
297 | );
298 | } else {
299 | return CupertinoPageTransition(
300 | primaryRouteAnimation: animation,
301 | secondaryRouteAnimation: secondaryAnimation,
302 | linearTransition: linearTransition,
303 | child: _CupertinoBackGestureDetector(
304 | enabledCallback: () => _isPopGestureEnabled(route),
305 | getWillPopCallback: () => _getRouteWillPopCallbacks(route),
306 | onStartPopGesture: () => _startPopGesture(route),
307 | child: child,
308 | ),
309 | );
310 | }
311 | }
312 |
313 | @override
314 | Widget buildTransitions(BuildContext context, Animation animation,
315 | Animation secondaryAnimation, Widget child) {
316 | return buildPageTransitions(
317 | this, context, animation, secondaryAnimation, child);
318 | }
319 | }
320 |
321 | /// A modal route that replaces the entire screen with an iOS transition.
322 | ///
323 | /// {@macro flutter.cupertino.cupertinoRouteTransitionMixin}
324 | ///
325 | /// By default, when a modal route is replaced by another, the previous route
326 | /// remains in memory. To free all the resources when this is not necessary, set
327 | /// [maintainState] to false.
328 | ///
329 | /// The type `T` specifies the return type of the route which can be supplied as
330 | /// the route is popped from the stack via [Navigator.pop] when an optional
331 | /// `result` can be provided.
332 | ///
333 | /// See also:
334 | ///
335 | /// * [CupertinoRouteTransitionMixin], for a mixin that provides iOS transition
336 | /// for this modal route.
337 | /// * [MaterialPageRoute], for an adaptive [PageRoute] that uses a
338 | /// platform-appropriate transition.
339 | /// * [CupertinoPageScaffold], for applications that have one page with a fixed
340 | /// navigation bar on top.
341 | /// * [CupertinoTabScaffold], for applications that have a tab bar at the
342 | /// bottom with multiple pages.
343 | /// * [CupertinoPage], for a [Page] version of this class.
344 | class CupertinoPageRoute extends PageRoute
345 | with CupertinoRouteTransitionMixin {
346 | /// Creates a page route for use in an iOS designed app.
347 | ///
348 | /// The [builder], [maintainState], and [fullscreenDialog] arguments must not
349 | /// be null.
350 | CupertinoPageRoute({
351 | required this.builder,
352 | this.title,
353 | RouteSettings? settings,
354 | this.maintainState = true,
355 | bool fullscreenDialog = false,
356 | }) : assert(builder != null),
357 | assert(maintainState != null),
358 | assert(fullscreenDialog != null),
359 | super(settings: settings, fullscreenDialog: fullscreenDialog) {
360 | assert(opaque);
361 | }
362 |
363 | /// Builds the primary contents of the route.
364 | final WidgetBuilder builder;
365 |
366 | @override
367 | Widget buildContent(BuildContext context) => builder(context);
368 |
369 | @override
370 | final String? title;
371 |
372 | @override
373 | final bool maintainState;
374 |
375 | @override
376 | String get debugLabel => '${super.debugLabel}(${settings.name})';
377 | }
378 |
379 | // A page-based version of CupertinoPageRoute.
380 | //
381 | // This route uses the builder from the page to build its content. This ensures
382 | // the content is up to date after page updates.
383 | class _PageBasedCupertinoPageRoute extends PageRoute
384 | with CupertinoRouteTransitionMixin {
385 | _PageBasedCupertinoPageRoute({
386 | required CupertinoPage page,
387 | }) : assert(page != null),
388 | super(settings: page) {
389 | assert(opaque);
390 | }
391 |
392 | CupertinoPage get _page => settings as CupertinoPage;
393 |
394 | @override
395 | Widget buildContent(BuildContext context) => _page.child;
396 |
397 | @override
398 | String? get title => _page.title;
399 |
400 | @override
401 | bool get maintainState => _page.maintainState;
402 |
403 | @override
404 | bool get fullscreenDialog => _page.fullscreenDialog;
405 |
406 | @override
407 | String get debugLabel => '${super.debugLabel}(${_page.name})';
408 | }
409 |
410 | /// A page that creates a cupertino style [PageRoute].
411 | ///
412 | /// {@macro flutter.cupertino.cupertinoRouteTransitionMixin}
413 | ///
414 | /// By default, when a created modal route is replaced by another, the previous
415 | /// route remains in memory. To free all the resources when this is not
416 | /// necessary, set [maintainState] to false.
417 | ///
418 | /// The type `T` specifies the return type of the route which can be supplied as
419 | /// the route is popped from the stack via [Navigator.transitionDelegate] by
420 | /// providing the optional `result` argument to the
421 | /// [RouteTransitionRecord.markForPop] in the [TransitionDelegate.resolve].
422 | ///
423 | /// See also:
424 | ///
425 | /// * [CupertinoPageRoute], for a [PageRoute] version of this class.
426 | class CupertinoPage extends Page {
427 | /// Creates a cupertino page.
428 | const CupertinoPage({
429 | required this.child,
430 | this.maintainState = true,
431 | this.title,
432 | this.fullscreenDialog = false,
433 | LocalKey? key,
434 | String? name,
435 | Object? arguments,
436 | String? restorationId,
437 | }) : assert(child != null),
438 | assert(maintainState != null),
439 | assert(fullscreenDialog != null),
440 | super(
441 | key: key,
442 | name: name,
443 | arguments: arguments,
444 | restorationId: restorationId);
445 |
446 | /// The content to be shown in the [Route] created by this page.
447 | final Widget child;
448 |
449 | /// {@macro flutter.cupertino.CupertinoRouteTransitionMixin.title}
450 | final String? title;
451 |
452 | /// {@macro flutter.widgets.ModalRoute.maintainState}
453 | final bool maintainState;
454 |
455 | /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
456 | final bool fullscreenDialog;
457 |
458 | @override
459 | Route createRoute(BuildContext context) {
460 | return _PageBasedCupertinoPageRoute(page: this);
461 | }
462 | }
463 |
464 | /// Provides an iOS-style page transition animation.
465 | ///
466 | /// The page slides in from the right and exits in reverse. It also shifts to the left in
467 | /// a parallax motion when another page enters to cover it.
468 | class CupertinoPageTransition extends StatelessWidget {
469 | /// Creates an iOS-style page transition.
470 | ///
471 | /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
472 | /// when this screen is being pushed.
473 | /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
474 | /// when another screen is being pushed on top of this one.
475 | /// * `linearTransition` is whether to perform the transitions linearly.
476 | /// Used to precisely track back gesture drags.
477 | CupertinoPageTransition({
478 | Key? key,
479 | required Animation primaryRouteAnimation,
480 | required Animation secondaryRouteAnimation,
481 | required this.child,
482 | required bool linearTransition,
483 | }) : assert(linearTransition != null),
484 | _primaryPositionAnimation = (linearTransition
485 | ? primaryRouteAnimation
486 | : CurvedAnimation(
487 | // The curves below have been rigorously derived from plots of native
488 | // iOS animation frames. Specifically, a video was taken of a page
489 | // transition animation and the distance in each frame that the page
490 | // moved was measured. A best fit bezier curve was the fitted to the
491 | // point set, which is linearToEaseIn. Conversely, easeInToLinear is the
492 | // reflection over the origin of linearToEaseIn.
493 | parent: primaryRouteAnimation,
494 | curve: Curves.linearToEaseOut,
495 | reverseCurve: Curves.easeInToLinear,
496 | ))
497 | .drive(_kRightMiddleTween),
498 | _secondaryPositionAnimation = (linearTransition
499 | ? secondaryRouteAnimation
500 | : CurvedAnimation(
501 | parent: secondaryRouteAnimation,
502 | curve: Curves.linearToEaseOut,
503 | reverseCurve: Curves.easeInToLinear,
504 | ))
505 | .drive(_kMiddleLeftTween),
506 | _primaryShadowAnimation = (linearTransition
507 | ? primaryRouteAnimation
508 | : CurvedAnimation(
509 | parent: primaryRouteAnimation,
510 | curve: Curves.linearToEaseOut,
511 | ))
512 | .drive(_CupertinoEdgeShadowDecoration.kTween),
513 | super(key: key);
514 |
515 | // When this page is coming in to cover another page.
516 | final Animation _primaryPositionAnimation;
517 | // When this page is becoming covered by another page.
518 | final Animation _secondaryPositionAnimation;
519 | final Animation _primaryShadowAnimation;
520 |
521 | /// The widget below this widget in the tree.
522 | final Widget child;
523 |
524 | @override
525 | Widget build(BuildContext context) {
526 | assert(debugCheckHasDirectionality(context));
527 | final TextDirection textDirection = Directionality.of(context);
528 | return SlideTransition(
529 | position: _secondaryPositionAnimation,
530 | textDirection: textDirection,
531 | transformHitTests: false,
532 | child: SlideTransition(
533 | position: _primaryPositionAnimation,
534 | textDirection: textDirection,
535 | child: DecoratedBoxTransition(
536 | decoration: _primaryShadowAnimation,
537 | child: child,
538 | ),
539 | ),
540 | );
541 | }
542 | }
543 |
544 | /// An iOS-style transition used for summoning fullscreen dialogs.
545 | ///
546 | /// For example, used when creating a new calendar event by bringing in the next
547 | /// screen from the bottom.
548 | class CupertinoFullscreenDialogTransition extends StatelessWidget {
549 | /// Creates an iOS-style transition used for summoning fullscreen dialogs.
550 | ///
551 | /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
552 | /// when this screen is being pushed.
553 | /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
554 | /// when another screen is being pushed on top of this one.
555 | /// * `linearTransition` is whether to perform the secondary transition linearly.
556 | /// Used to precisely track back gesture drags.
557 | CupertinoFullscreenDialogTransition({
558 | Key? key,
559 | required Animation primaryRouteAnimation,
560 | required Animation secondaryRouteAnimation,
561 | required this.child,
562 | required bool linearTransition,
563 | }) : _positionAnimation = CurvedAnimation(
564 | parent: primaryRouteAnimation,
565 | curve: Curves.linearToEaseOut,
566 | // The curve must be flipped so that the reverse animation doesn't play
567 | // an ease-in curve, which iOS does not use.
568 | reverseCurve: Curves.linearToEaseOut.flipped,
569 | ).drive(_kBottomUpTween),
570 | _secondaryPositionAnimation = (linearTransition
571 | ? secondaryRouteAnimation
572 | : CurvedAnimation(
573 | parent: secondaryRouteAnimation,
574 | curve: Curves.linearToEaseOut,
575 | reverseCurve: Curves.easeInToLinear,
576 | ))
577 | .drive(_kMiddleLeftTween),
578 | super(key: key);
579 |
580 | final Animation _positionAnimation;
581 | // When this page is becoming covered by another page.
582 | final Animation _secondaryPositionAnimation;
583 |
584 | /// The widget below this widget in the tree.
585 | final Widget child;
586 |
587 | @override
588 | Widget build(BuildContext context) {
589 | assert(debugCheckHasDirectionality(context));
590 | final TextDirection textDirection = Directionality.of(context);
591 | return SlideTransition(
592 | position: _secondaryPositionAnimation,
593 | textDirection: textDirection,
594 | transformHitTests: false,
595 | child: SlideTransition(
596 | position: _positionAnimation,
597 | child: child,
598 | ),
599 | );
600 | }
601 | }
602 |
603 | /// This is the widget side of [_CupertinoBackGestureController].
604 | ///
605 | /// This widget provides a gesture recognizer which, when it determines the
606 | /// route can be closed with a back gesture, creates the controller and
607 | /// feeds it the input from the gesture recognizer.
608 | ///
609 | /// The gesture data is converted from absolute coordinates to logical
610 | /// coordinates by this widget.
611 | ///
612 | /// The type `T` specifies the return type of the route with which this gesture
613 | /// detector is associated.
614 | class _CupertinoBackGestureDetector extends StatefulWidget {
615 | const _CupertinoBackGestureDetector({
616 | Key? key,
617 | required this.enabledCallback,
618 | required this.getWillPopCallback,
619 | required this.onStartPopGesture,
620 | required this.child,
621 | }) : assert(enabledCallback != null),
622 | assert(getWillPopCallback != null),
623 | assert(onStartPopGesture != null),
624 | assert(child != null),
625 | super(key: key);
626 |
627 | final Widget child;
628 |
629 | final ValueGetter enabledCallback;
630 |
631 | final Function() getWillPopCallback;
632 |
633 | final ValueGetter<_CupertinoBackGestureController> onStartPopGesture;
634 |
635 | @override
636 | _CupertinoBackGestureDetectorState createState() =>
637 | _CupertinoBackGestureDetectorState();
638 | }
639 |
640 | class _CupertinoBackGestureDetectorState
641 | extends State<_CupertinoBackGestureDetector> {
642 | _CupertinoBackGestureController? _backGestureController;
643 |
644 | late HorizontalDragGestureRecognizer _recognizer;
645 |
646 | Future Function()? _willPopCallback;
647 |
648 | @override
649 | void initState() {
650 | super.initState();
651 | _recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
652 | ..onStart = _handleDragStart
653 | ..onUpdate = _handleDragUpdate
654 | ..onEnd = _handleDragEnd
655 | ..onCancel = _handleDragCancel;
656 | }
657 |
658 | @override
659 | void dispose() {
660 | _recognizer.dispose();
661 | super.dispose();
662 | }
663 |
664 | void _handleDragStart(DragStartDetails details) {
665 | assert(mounted);
666 | assert(_backGestureController == null);
667 | _willPopCallback = widget.getWillPopCallback();
668 | _backGestureController = widget.onStartPopGesture();
669 | }
670 |
671 | void _handleDragUpdate(DragUpdateDetails details) {
672 | assert(mounted);
673 | // assert(_backGestureController != null);
674 |
675 | if (_willPopCallback != null &&
676 | details.globalPosition.dx > context.size!.width * _kMaxSwipeDistance) {
677 | _willPopCallback!();
678 | _handleDragCancel();
679 | }
680 |
681 | _backGestureController?.dragUpdate(
682 | _convertToLogical(details.primaryDelta! / context.size!.width));
683 | }
684 |
685 | void _handleDragEnd(DragEndDetails details) {
686 | assert(mounted);
687 | // assert(_backGestureController != null);
688 | _backGestureController?.dragEnd(_convertToLogical(
689 | details.velocity.pixelsPerSecond.dx / context.size!.width));
690 |
691 | _backGestureController = null;
692 | _willPopCallback = null;
693 | }
694 |
695 | void _handleDragCancel() {
696 | assert(mounted);
697 | // This can be called even if start is not called, paired with the "down" event
698 | // that we don't consider here.
699 | _backGestureController?.dragEnd(0.0);
700 | _backGestureController = null;
701 | }
702 |
703 | void _handlePointerDown(PointerDownEvent event) {
704 | if (widget.enabledCallback()) _recognizer.addPointer(event);
705 | }
706 |
707 | double _convertToLogical(double value) {
708 | switch (Directionality.of(context)) {
709 | case TextDirection.rtl:
710 | return -value;
711 | case TextDirection.ltr:
712 | return value;
713 | }
714 | }
715 |
716 | @override
717 | Widget build(BuildContext context) {
718 | assert(debugCheckHasDirectionality(context));
719 | // For devices with notches, the drag area needs to be larger on the side
720 | // that has the notch.
721 | double dragAreaWidth = Directionality.of(context) == TextDirection.ltr
722 | ? MediaQuery.of(context).padding.left
723 | : MediaQuery.of(context).padding.right;
724 | dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth);
725 | return Stack(
726 | fit: StackFit.passthrough,
727 | children: [
728 | widget.child,
729 | PositionedDirectional(
730 | start: 0.0,
731 | width: dragAreaWidth,
732 | top: 0.0,
733 | bottom: 0.0,
734 | child: Listener(
735 | onPointerDown: _handlePointerDown,
736 | behavior: HitTestBehavior.translucent,
737 | ),
738 | ),
739 | ],
740 | );
741 | }
742 | }
743 |
744 | /// A controller for an iOS-style back gesture.
745 | ///
746 | /// This is created by a [CupertinoPageRoute] in response from a gesture caught
747 | /// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input
748 | /// from the gesture. It controls the animation controller owned by the route,
749 | /// based on the input provided by the gesture detector.
750 | ///
751 | /// This class works entirely in logical coordinates (0.0 is new page dismissed,
752 | /// 1.0 is new page on top).
753 | ///
754 | /// The type `T` specifies the return type of the route with which this gesture
755 | /// detector controller is associated.
756 | class _CupertinoBackGestureController {
757 | /// Creates a controller for an iOS-style back gesture.
758 | ///
759 | /// The [navigator] and [controller] arguments must not be null.
760 | _CupertinoBackGestureController({
761 | required this.navigator,
762 | required this.controller,
763 | }) : assert(navigator != null),
764 | assert(controller != null) {
765 | navigator.didStartUserGesture();
766 | }
767 |
768 | final AnimationController controller;
769 | final NavigatorState navigator;
770 |
771 | /// The drag gesture has changed by [fractionalDelta]. The total range of the
772 | /// drag should be 0.0 to 1.0.
773 | void dragUpdate(double delta) {
774 | controller.value -= delta;
775 | }
776 |
777 | /// The drag gesture has ended with a horizontal motion of
778 | /// [fractionalVelocity] as a fraction of screen width per second.
779 | void dragEnd(double velocity) {
780 | // Fling in the appropriate direction.
781 | // AnimationController.fling is guaranteed to
782 | // take at least one frame.
783 | //
784 | // This curve has been determined through rigorously eyeballing native iOS
785 | // animations.
786 | const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
787 | final bool animateForward;
788 |
789 | // If the user releases the page before mid screen with sufficient velocity,
790 | // or after mid screen, we should animate the page out. Otherwise, the page
791 | // should be animated back in.
792 | if (velocity.abs() >= _kMinFlingVelocity)
793 | animateForward = velocity <= 0;
794 | else
795 | animateForward = controller.value > 0.5;
796 |
797 | if (animateForward) {
798 | // The closer the panel is to dismissing, the shorter the animation is.
799 | // We want to cap the animation time, but we want to use a linear curve
800 | // to determine it.
801 | final int droppedPageForwardAnimationTime = min(
802 | lerpDouble(
803 | _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!
804 | .floor(),
805 | _kMaxPageBackAnimationTime,
806 | );
807 | controller.animateTo(1.0,
808 | duration: Duration(milliseconds: droppedPageForwardAnimationTime),
809 | curve: animationCurve);
810 | } else {
811 | // This route is destined to pop at this point. Reuse navigator's pop.
812 | navigator.pop();
813 |
814 | // The popping may have finished inline if already at the target destination.
815 | if (controller.isAnimating) {
816 | // Otherwise, use a custom popping animation duration and curve.
817 | final int droppedPageBackAnimationTime = lerpDouble(
818 | 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!
819 | .floor();
820 | controller.animateBack(0.0,
821 | duration: Duration(milliseconds: droppedPageBackAnimationTime),
822 | curve: animationCurve);
823 | }
824 | }
825 |
826 | if (controller.isAnimating) {
827 | // Keep the userGestureInProgress in true state so we don't change the
828 | // curve of the page transition mid-flight since CupertinoPageTransition
829 | // depends on userGestureInProgress.
830 | late AnimationStatusListener animationStatusCallback;
831 | animationStatusCallback = (AnimationStatus status) {
832 | navigator.didStopUserGesture();
833 | controller.removeStatusListener(animationStatusCallback);
834 | };
835 | controller.addStatusListener(animationStatusCallback);
836 | } else {
837 | navigator.didStopUserGesture();
838 | }
839 | }
840 | }
841 |
842 | // A custom [Decoration] used to paint an extra shadow on the start edge of the
843 | // box it's decorating. It's like a [BoxDecoration] with only a gradient except
844 | // it paints on the start side of the box instead of behind the box.
845 | class _CupertinoEdgeShadowDecoration extends Decoration {
846 | const _CupertinoEdgeShadowDecoration._([this._colors]);
847 |
848 | static DecorationTween kTween = DecorationTween(
849 | begin: const _CupertinoEdgeShadowDecoration._(), // No decoration initially.
850 | end: const _CupertinoEdgeShadowDecoration._(
851 | // Eyeballed gradient used to mimic a drop shadow on the start side only.
852 | [
853 | Color(0x04000000),
854 | Color(0x00000000),
855 | ],
856 | ),
857 | );
858 |
859 | // Colors used to paint a gradient at the start edge of the box it is
860 | // decorating.
861 | //
862 | // The first color in the list is used at the start of the gradient, which
863 | // is located at the start edge of the decorated box.
864 | //
865 | // If this is null, no shadow is drawn.
866 | //
867 | // The list must have at least two colors in it (otherwise it would not be a
868 | // gradient).
869 | final List? _colors;
870 |
871 | // Linearly interpolate between two edge shadow decorations decorations.
872 | //
873 | // The `t` argument represents position on the timeline, with 0.0 meaning
874 | // that the interpolation has not started, returning `a` (or something
875 | // equivalent to `a`), 1.0 meaning that the interpolation has finished,
876 | // returning `b` (or something equivalent to `b`), and values in between
877 | // meaning that the interpolation is at the relevant point on the timeline
878 | // between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
879 | // 1.0, so negative values and values greater than 1.0 are valid (and can
880 | // easily be generated by curves such as [Curves.elasticInOut]).
881 | //
882 | // Values for `t` are usually obtained from an [Animation], such as
883 | // an [AnimationController].
884 | //
885 | // See also:
886 | //
887 | // * [Decoration.lerp].
888 | static _CupertinoEdgeShadowDecoration? lerp(
889 | _CupertinoEdgeShadowDecoration? a,
890 | _CupertinoEdgeShadowDecoration? b,
891 | double t,
892 | ) {
893 | assert(t != null);
894 | if (a == null && b == null) return null;
895 |
896 | if (a == null)
897 | return b!._colors == null
898 | ? b
899 | : _CupertinoEdgeShadowDecoration._(b._colors!
900 | .map((Color color) => Color.lerp(null, color, t)!)
901 | .toList());
902 | if (b == null)
903 | return a._colors == null
904 | ? a
905 | : _CupertinoEdgeShadowDecoration._(a._colors!
906 | .map((Color color) => Color.lerp(null, color, 1.0 - t)!)
907 | .toList());
908 | assert(b._colors != null || a._colors != null);
909 | // If it ever becomes necessary, we could allow decorations with different
910 | // length' here, similarly to how it is handled in [LinearGradient.lerp].
911 | assert(b._colors == null ||
912 | a._colors == null ||
913 | a._colors!.length == b._colors!.length);
914 | return _CupertinoEdgeShadowDecoration._(
915 | [
916 | for (int i = 0; i < b._colors!.length; i += 1)
917 | Color.lerp(a._colors?[i], b._colors?[i], t)!,
918 | ],
919 | );
920 | }
921 |
922 | @override
923 | _CupertinoEdgeShadowDecoration lerpFrom(Decoration? a, double t) {
924 | if (a is _CupertinoEdgeShadowDecoration)
925 | return _CupertinoEdgeShadowDecoration.lerp(a, this, t)!;
926 | return _CupertinoEdgeShadowDecoration.lerp(null, this, t)!;
927 | }
928 |
929 | @override
930 | _CupertinoEdgeShadowDecoration lerpTo(Decoration? b, double t) {
931 | if (b is _CupertinoEdgeShadowDecoration)
932 | return _CupertinoEdgeShadowDecoration.lerp(this, b, t)!;
933 | return _CupertinoEdgeShadowDecoration.lerp(this, null, t)!;
934 | }
935 |
936 | @override
937 | _CupertinoEdgeShadowPainter createBoxPainter([VoidCallback? onChanged]) {
938 | return _CupertinoEdgeShadowPainter(this, onChanged);
939 | }
940 |
941 | @override
942 | bool operator ==(Object other) {
943 | if (other.runtimeType != runtimeType) return false;
944 | return other is _CupertinoEdgeShadowDecoration && other._colors == _colors;
945 | }
946 |
947 | @override
948 | int get hashCode => _colors.hashCode;
949 |
950 | @override
951 | void debugFillProperties(DiagnosticPropertiesBuilder properties) {
952 | super.debugFillProperties(properties);
953 | properties.add(IterableProperty('colors', _colors));
954 | }
955 | }
956 |
957 | /// A [BoxPainter] used to draw the page transition shadow using gradients.
958 | class _CupertinoEdgeShadowPainter extends BoxPainter {
959 | _CupertinoEdgeShadowPainter(
960 | this._decoration,
961 | VoidCallback? onChange,
962 | ) : assert(_decoration != null),
963 | assert(_decoration._colors == null || _decoration._colors!.length > 1),
964 | super(onChange);
965 |
966 | final _CupertinoEdgeShadowDecoration _decoration;
967 |
968 | @override
969 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
970 | final List? colors = _decoration._colors;
971 | if (colors == null) {
972 | return;
973 | }
974 |
975 | // The following code simulates drawing a [LinearGradient] configured as
976 | // follows:
977 | //
978 | // LinearGradient(
979 | // begin: AlignmentDirectional(0.90, 0.0), // Spans 5% of the page.
980 | // colors: _decoration._colors,
981 | // )
982 | //
983 | // A performance evaluation on Feb 8, 2021 showed, that drawing the gradient
984 | // manually as implemented below is more performant than relying on
985 | // [LinearGradient.createShader] because compiling that shader takes a long
986 | // time. On an iPhone XR, the implementation below reduced the worst frame
987 | // time for a cupertino page transition of a newly installed app from ~95ms
988 | // down to ~30ms, mainly because there's no longer a need to compile a
989 | // shader for the LinearGradient.
990 | //
991 | // The implementation below divides the width of the shadow into multiple
992 | // bands of equal width, one for each color interval defined by
993 | // `_decoration._colors`. Band x is filled with a gradient going from
994 | // `_decoration._colors[x]` to `_decoration._colors[x + 1]` by drawing a
995 | // bunch of 1px wide rects. The rects change their color by lerping between
996 | // the two colors that define the interval of the band.
997 |
998 | // Shadow spans 5% of the page.
999 | final double shadowWidth = 0.05 * configuration.size!.width;
1000 | final double shadowHeight = configuration.size!.height;
1001 | final double bandWidth = shadowWidth / (colors.length - 1);
1002 |
1003 | final TextDirection? textDirection = configuration.textDirection;
1004 | assert(textDirection != null);
1005 | final double start;
1006 | final double shadowDirection; // -1 for ltr, 1 for rtl.
1007 | switch (textDirection!) {
1008 | case TextDirection.rtl:
1009 | start = offset.dx + configuration.size!.width;
1010 | shadowDirection = 1;
1011 | break;
1012 | case TextDirection.ltr:
1013 | start = offset.dx;
1014 | shadowDirection = -1;
1015 | break;
1016 | }
1017 |
1018 | int bandColorIndex = 0;
1019 | for (int dx = 0; dx < shadowWidth; dx += 1) {
1020 | if (dx ~/ bandWidth != bandColorIndex) {
1021 | bandColorIndex += 1;
1022 | }
1023 | final Paint paint = Paint()
1024 | ..color = Color.lerp(colors[bandColorIndex], colors[bandColorIndex + 1],
1025 | (dx % bandWidth) / bandWidth)!;
1026 | final double x = start + shadowDirection * dx;
1027 | canvas.drawRect(
1028 | Rect.fromLTWH(x - 1.0, offset.dy, 1.0, shadowHeight), paint);
1029 | }
1030 | }
1031 | }
1032 |
1033 | /// A route that shows a modal iOS-style popup that slides up from the
1034 | /// bottom of the screen.
1035 | ///
1036 | /// Such a popup is an alternative to a menu or a dialog and prevents the user
1037 | /// from interacting with the rest of the app.
1038 | ///
1039 | /// It is used internally by [showCupertinoModalPopup] or can be directly pushed
1040 | /// onto the [Navigator] stack to enable state restoration. See
1041 | /// [showCupertinoModalPopup] for a state restoration app example.
1042 | ///
1043 | /// The `barrierColor` argument determines the [Color] of the barrier underneath
1044 | /// the popup. When unspecified, the barrier color defaults to a light opacity
1045 | /// black scrim based on iOS's dialog screens. To correctly have iOS resolve
1046 | /// to the appropriate modal colors, pass in
1047 | /// `CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context)`.
1048 | ///
1049 | /// The `barrierDismissible` argument determines whether clicking outside the
1050 | /// popup results in dismissal. It is `true` by default.
1051 | ///
1052 | /// The `semanticsDismissible` argument is used to determine whether the
1053 | /// semantics of the modal barrier are included in the semantics tree.
1054 | ///
1055 | /// The `routeSettings` argument is used to provide [RouteSettings] to the
1056 | /// created Route.
1057 | ///
1058 | /// {@macro flutter.widgets.RawDialogRoute}
1059 | ///
1060 | /// See also:
1061 | ///
1062 | /// * [DisplayFeatureSubScreen], which documents the specifics of how
1063 | /// [DisplayFeature]s can split the screen into sub-screens.
1064 | /// * [CupertinoActionSheet], which is the widget usually returned by the
1065 | /// `builder` argument.
1066 | /// *
1067 | class CupertinoModalPopupRoute extends PopupRoute {
1068 | /// A route that shows a modal iOS-style popup that slides up from the
1069 | /// bottom of the screen.
1070 | CupertinoModalPopupRoute({
1071 | required this.builder,
1072 | this.barrierLabel = 'Dismiss',
1073 | this.barrierColor = kCupertinoModalBarrierColor,
1074 | bool barrierDismissible = true,
1075 | bool? semanticsDismissible,
1076 | ImageFilter? filter,
1077 | RouteSettings? settings,
1078 | this.anchorPoint,
1079 | }) : super(
1080 | filter: filter,
1081 | settings: settings,
1082 | ) {
1083 | _barrierDismissible = barrierDismissible;
1084 | _semanticsDismissible = semanticsDismissible;
1085 | }
1086 |
1087 | /// A builder that builds the widget tree for the [CupertinoModalPopupRoute].
1088 | ///
1089 | /// The `builder` argument typically builds a [CupertinoActionSheet] widget.
1090 | ///
1091 | /// Content below the widget is dimmed with a [ModalBarrier]. The widget built
1092 | /// by the `builder` does not share a context with the route it was originally
1093 | /// built from. Use a [StatefulBuilder] or a custom [StatefulWidget] if the
1094 | /// widget needs to update dynamically.
1095 | final WidgetBuilder builder;
1096 |
1097 | bool? _barrierDismissible;
1098 |
1099 | bool? _semanticsDismissible;
1100 |
1101 | @override
1102 | final String barrierLabel;
1103 |
1104 | @override
1105 | final Color? barrierColor;
1106 |
1107 | @override
1108 | bool get barrierDismissible => _barrierDismissible ?? true;
1109 |
1110 | @override
1111 | bool get semanticsDismissible => _semanticsDismissible ?? false;
1112 |
1113 | @override
1114 | Duration get transitionDuration => _kModalPopupTransitionDuration;
1115 |
1116 | Animation? _animation;
1117 |
1118 | late Tween _offsetTween;
1119 |
1120 | /// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
1121 | final Offset? anchorPoint;
1122 |
1123 | @override
1124 | Animation createAnimation() {
1125 | assert(_animation == null);
1126 | _animation = CurvedAnimation(
1127 | parent: super.createAnimation(),
1128 |
1129 | // These curves were initially measured from native iOS horizontal page
1130 | // route animations and seemed to be a good match here as well.
1131 | curve: Curves.linearToEaseOut,
1132 | reverseCurve: Curves.linearToEaseOut.flipped,
1133 | );
1134 | _offsetTween = Tween(
1135 | begin: const Offset(0.0, 1.0),
1136 | end: Offset.zero,
1137 | );
1138 | return _animation!;
1139 | }
1140 |
1141 | @override
1142 | Widget buildPage(BuildContext context, Animation animation,
1143 | Animation secondaryAnimation) {
1144 | return CupertinoUserInterfaceLevel(
1145 | data: CupertinoUserInterfaceLevelData.elevated,
1146 | child: DisplayFeatureSubScreen(
1147 | anchorPoint: anchorPoint,
1148 | child: Builder(builder: builder),
1149 | ),
1150 | );
1151 | }
1152 |
1153 | @override
1154 | Widget buildTransitions(BuildContext context, Animation animation,
1155 | Animation secondaryAnimation, Widget child) {
1156 | return Align(
1157 | alignment: Alignment.bottomCenter,
1158 | child: FractionalTranslation(
1159 | translation: _offsetTween.evaluate(_animation!),
1160 | child: child,
1161 | ),
1162 | );
1163 | }
1164 | }
1165 |
1166 | /// Shows a modal iOS-style popup that slides up from the bottom of the screen.
1167 | ///
1168 | /// Such a popup is an alternative to a menu or a dialog and prevents the user
1169 | /// from interacting with the rest of the app.
1170 | ///
1171 | /// The `context` argument is used to look up the [Navigator] for the popup.
1172 | /// It is only used when the method is called. Its corresponding widget can be
1173 | /// safely removed from the tree before the popup is closed.
1174 | ///
1175 | /// The `barrierColor` argument determines the [Color] of the barrier underneath
1176 | /// the popup. When unspecified, the barrier color defaults to a light opacity
1177 | /// black scrim based on iOS's dialog screens.
1178 | ///
1179 | /// The `barrierDismissible` argument determines whether clicking outside the
1180 | /// popup results in dismissal. It is `true` by default.
1181 | ///
1182 | /// The `useRootNavigator` argument is used to determine whether to push the
1183 | /// popup to the [Navigator] furthest from or nearest to the given `context`. It
1184 | /// is `false` by default.
1185 | ///
1186 | /// The `semanticsDismissible` argument is used to determine whether the
1187 | /// semantics of the modal barrier are included in the semantics tree.
1188 | ///
1189 | /// The `routeSettings` argument is used to provide [RouteSettings] to the
1190 | /// created Route.
1191 | ///
1192 | /// The `builder` argument typically builds a [CupertinoActionSheet] widget.
1193 | /// Content below the widget is dimmed with a [ModalBarrier]. The widget built
1194 | /// by the `builder` does not share a context with the location that
1195 | /// `showCupertinoModalPopup` is originally called from. Use a
1196 | /// [StatefulBuilder] or a custom [StatefulWidget] if the widget needs to
1197 | /// update dynamically.
1198 | ///
1199 | /// {@macro flutter.widgets.RawDialogRoute}
1200 | ///
1201 | /// Returns a `Future` that resolves to the value that was passed to
1202 | /// [Navigator.pop] when the popup was closed.
1203 | ///
1204 | /// ### State Restoration in Modals
1205 | ///
1206 | /// Using this method will not enable state restoration for the modal. In order
1207 | /// to enable state restoration for a modal, use [Navigator.restorablePush]
1208 | /// or [Navigator.restorablePushNamed] with [CupertinoModalPopupRoute].
1209 | ///
1210 | /// For more information about state restoration, see [RestorationManager].
1211 | ///
1212 | /// {@tool sample}
1213 | /// This sample demonstrates how to create a restorable Cupertino modal route.
1214 | /// This is accomplished by enabling state restoration by specifying
1215 | /// [CupertinoApp.restorationScopeId] and using [Navigator.restorablePush] to
1216 | /// push [CupertinoModalPopupRoute] when the [CupertinoButton] is tapped.
1217 | ///
1218 | /// {@macro flutter.widgets.RestorationManager}
1219 | ///
1220 | /// ** See code in examples/api/lib/cupertino/route/show_cupertino_modal_popup.0.dart **
1221 | /// {@end-tool}
1222 | ///
1223 | /// See also:
1224 | ///
1225 | /// * [DisplayFeatureSubScreen], which documents the specifics of how
1226 | /// [DisplayFeature]s can split the screen into sub-screens.
1227 | /// * [CupertinoActionSheet], which is the widget usually returned by the
1228 | /// `builder` argument to [showCupertinoModalPopup].
1229 | /// *
1230 | Future showCupertinoModalPopup({
1231 | required BuildContext context,
1232 | required WidgetBuilder builder,
1233 | ImageFilter? filter,
1234 | Color barrierColor = kCupertinoModalBarrierColor,
1235 | bool barrierDismissible = true,
1236 | bool useRootNavigator = true,
1237 | bool? semanticsDismissible,
1238 | RouteSettings? routeSettings,
1239 | Offset? anchorPoint,
1240 | }) {
1241 | assert(useRootNavigator != null);
1242 | return Navigator.of(context, rootNavigator: useRootNavigator).push(
1243 | CupertinoModalPopupRoute(
1244 | builder: builder,
1245 | filter: filter,
1246 | barrierColor: CupertinoDynamicColor.resolve(barrierColor, context),
1247 | barrierDismissible: barrierDismissible,
1248 | semanticsDismissible: semanticsDismissible,
1249 | settings: routeSettings,
1250 | anchorPoint: anchorPoint,
1251 | ),
1252 | );
1253 | }
1254 |
1255 | // The curve and initial scale values were mostly eyeballed from iOS, however
1256 | // they reuse the same animation curve that was modeled after native page
1257 | // transitions.
1258 | final Animatable _dialogScaleTween = Tween(begin: 1.3, end: 1.0)
1259 | .chain(CurveTween(curve: Curves.linearToEaseOut));
1260 |
1261 | Widget _buildCupertinoDialogTransitions(
1262 | BuildContext context,
1263 | Animation animation,
1264 | Animation secondaryAnimation,
1265 | Widget child) {
1266 | final CurvedAnimation fadeAnimation = CurvedAnimation(
1267 | parent: animation,
1268 | curve: Curves.easeInOut,
1269 | );
1270 | if (animation.status == AnimationStatus.reverse) {
1271 | return FadeTransition(
1272 | opacity: fadeAnimation,
1273 | child: child,
1274 | );
1275 | }
1276 | return FadeTransition(
1277 | opacity: fadeAnimation,
1278 | child: ScaleTransition(
1279 | scale: animation.drive(_dialogScaleTween),
1280 | child: child,
1281 | ),
1282 | );
1283 | }
1284 |
1285 | /// Displays an iOS-style dialog above the current contents of the app, with
1286 | /// iOS-style entrance and exit animations, modal barrier color, and modal
1287 | /// barrier behavior (by default, the dialog is not dismissible with a tap on
1288 | /// the barrier).
1289 | ///
1290 | /// This function takes a `builder` which typically builds a [CupertinoAlertDialog]
1291 | /// widget. Content below the dialog is dimmed with a [ModalBarrier]. The widget
1292 | /// returned by the `builder` does not share a context with the location that
1293 | /// `showCupertinoDialog` is originally called from. Use a [StatefulBuilder] or
1294 | /// a custom [StatefulWidget] if the dialog needs to update dynamically.
1295 | ///
1296 | /// The `context` argument is used to look up the [Navigator] for the dialog.
1297 | /// It is only used when the method is called. Its corresponding widget can
1298 | /// be safely removed from the tree before the dialog is closed.
1299 | ///
1300 | /// The `useRootNavigator` argument is used to determine whether to push the
1301 | /// dialog to the [Navigator] furthest from or nearest to the given `context`.
1302 | /// By default, `useRootNavigator` is `true` and the dialog route created by
1303 | /// this method is pushed to the root navigator.
1304 | ///
1305 | /// {@macro flutter.widgets.RawDialogRoute}
1306 | ///
1307 | /// If the application has multiple [Navigator] objects, it may be necessary to
1308 | /// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
1309 | /// dialog rather than just `Navigator.pop(context, result)`.
1310 | ///
1311 | /// Returns a [Future] that resolves to the value (if any) that was passed to
1312 | /// [Navigator.pop] when the dialog was closed.
1313 | ///
1314 | /// ### State Restoration in Dialogs
1315 | ///
1316 | /// Using this method will not enable state restoration for the dialog. In order
1317 | /// to enable state restoration for a dialog, use [Navigator.restorablePush]
1318 | /// or [Navigator.restorablePushNamed] with [CupertinoDialogRoute].
1319 | ///
1320 | /// For more information about state restoration, see [RestorationManager].
1321 | ///
1322 | /// {@tool sample}
1323 | /// This sample demonstrates how to create a restorable Cupertino dialog. This is
1324 | /// accomplished by enabling state restoration by specifying
1325 | /// [CupertinoApp.restorationScopeId] and using [Navigator.restorablePush] to
1326 | /// push [CupertinoDialogRoute] when the [CupertinoButton] is tapped.
1327 | ///
1328 | /// {@macro flutter.widgets.RestorationManager}
1329 | ///
1330 | /// ** See code in examples/api/lib/cupertino/route/show_cupertino_dialog.0.dart **
1331 | /// {@end-tool}
1332 | ///
1333 | /// See also:
1334 | ///
1335 | /// * [CupertinoAlertDialog], an iOS-style alert dialog.
1336 | /// * [showDialog], which displays a Material-style dialog.
1337 | /// * [showGeneralDialog], which allows for customization of the dialog popup.
1338 | /// * [DisplayFeatureSubScreen], which documents the specifics of how
1339 | /// [DisplayFeature]s can split the screen into sub-screens.
1340 | /// *
1341 | Future showCupertinoDialog({
1342 | required BuildContext context,
1343 | required WidgetBuilder builder,
1344 | String? barrierLabel,
1345 | bool useRootNavigator = true,
1346 | bool barrierDismissible = false,
1347 | RouteSettings? routeSettings,
1348 | Offset? anchorPoint,
1349 | }) {
1350 | assert(builder != null);
1351 | assert(useRootNavigator != null);
1352 |
1353 | return Navigator.of(context, rootNavigator: useRootNavigator)
1354 | .push(CupertinoDialogRoute(
1355 | builder: builder,
1356 | context: context,
1357 | barrierDismissible: barrierDismissible,
1358 | barrierLabel: barrierLabel,
1359 | barrierColor:
1360 | CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
1361 | settings: routeSettings,
1362 | anchorPoint: anchorPoint,
1363 | ));
1364 | }
1365 |
1366 | /// A dialog route that shows an iOS-style dialog.
1367 | ///
1368 | /// It is used internally by [showCupertinoDialog] or can be directly pushed
1369 | /// onto the [Navigator] stack to enable state restoration. See
1370 | /// [showCupertinoDialog] for a state restoration app example.
1371 | ///
1372 | /// This function takes a `builder` which typically builds a [Dialog] widget.
1373 | /// Content below the dialog is dimmed with a [ModalBarrier]. The widget
1374 | /// returned by the `builder` does not share a context with the location that
1375 | /// `showDialog` is originally called from. Use a [StatefulBuilder] or a
1376 | /// custom [StatefulWidget] if the dialog needs to update dynamically.
1377 | ///
1378 | /// The `context` argument is used to look up
1379 | /// [CupertinoLocalizations.modalBarrierDismissLabel], which provides the
1380 | /// modal with a localized accessibility label that will be used for the
1381 | /// modal's barrier. However, a custom `barrierLabel` can be passed in as well.
1382 | ///
1383 | /// The `barrierDismissible` argument is used to indicate whether tapping on the
1384 | /// barrier will dismiss the dialog. It is `true` by default and cannot be `null`.
1385 | ///
1386 | /// The `barrierColor` argument is used to specify the color of the modal
1387 | /// barrier that darkens everything below the dialog. If `null`, then
1388 | /// [CupertinoDynamicColor.resolve] is used to compute the modal color.
1389 | ///
1390 | /// The `settings` argument define the settings for this route. See
1391 | /// [RouteSettings] for details.
1392 | ///
1393 | /// {@macro flutter.widgets.RawDialogRoute}
1394 | ///
1395 | /// See also:
1396 | ///
1397 | /// * [showCupertinoDialog], which is a way to display
1398 | /// an iOS-style dialog.
1399 | /// * [showGeneralDialog], which allows for customization of the dialog popup.
1400 | /// * [showDialog], which displays a Material dialog.
1401 | /// * [DisplayFeatureSubScreen], which documents the specifics of how
1402 | /// [DisplayFeature]s can split the screen into sub-screens.
1403 | class CupertinoDialogRoute extends RawDialogRoute {
1404 | /// A dialog route that shows an iOS-style dialog.
1405 | CupertinoDialogRoute({
1406 | required WidgetBuilder builder,
1407 | required BuildContext context,
1408 | bool barrierDismissible = true,
1409 | Color? barrierColor,
1410 | String? barrierLabel,
1411 | // This transition duration was eyeballed comparing with iOS
1412 | Duration transitionDuration = const Duration(milliseconds: 250),
1413 | RouteTransitionsBuilder? transitionBuilder =
1414 | _buildCupertinoDialogTransitions,
1415 | RouteSettings? settings,
1416 | Offset? anchorPoint,
1417 | }) : assert(barrierDismissible != null),
1418 | super(
1419 | pageBuilder: (BuildContext context, Animation animation,
1420 | Animation secondaryAnimation) {
1421 | return builder(context);
1422 | },
1423 | barrierDismissible: barrierDismissible,
1424 | barrierLabel: barrierLabel ??
1425 | CupertinoLocalizations.of(context).modalBarrierDismissLabel,
1426 | barrierColor: barrierColor ??
1427 | CupertinoDynamicColor.resolve(
1428 | kCupertinoModalBarrierColor, context),
1429 | transitionDuration: transitionDuration,
1430 | transitionBuilder: transitionBuilder,
1431 | settings: settings,
1432 | anchorPoint: anchorPoint,
1433 | );
1434 | }
1435 |
--------------------------------------------------------------------------------