├── 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 | [![pub package](https://img.shields.io/pub/v/cupertino_will_pop_scope.svg)](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 | --------------------------------------------------------------------------------